Make your (Kotlin) code express itself

Because your code has feelings too

Prafull Mishra
ProAndroidDev

--

Its a balance you have to make

Let’s start this with a little experiment. Ready? What comes to your mind when you read the word — STATE ? 🤔

Assuming that you are a mobile or front-end developer, let me make a guess. You thought of state-based architectures like MVI (Model View Intent), MVVM or REDUX or some architecture on the same lines… Am I right?

Yeah, I know (We are all the same!) Also, I am considering that this means you know what are state-based architectures (SBAs), or at-least have a basic idea about them. Now that this is clear, let’s continue to my actual point.

State based a̶r̶c̶h̶i̶t̶e̶c̶t̶u̶r̶e̶ thinking

The primary goal of any state based system, is to clearly layout all the possible ways it can exist in. For instance, your laptop can be in following discrete states:

  • Switched on
  • Switched off
  • Sleep mode

None of these states can co-exist together. This separation is the biggest strength of state based architectures (SBAs). Why? Because by defining the states and drawing the line between them, you reduce the no. of states your application can be in, to a handful. Which can not only be easily tested, but also easier to debug if an issue is found later on. It becomes unambiguous.

This non-ambiguity is the key for keeping you code maintainable, testable and most importantly — readable

We often limit state-based thinking to just SBAs and never take it further. But this thinking approach, can be extended to just about any OOP code you write regardless of its architecture, which will not only increase your code’s quality but also make it more immune against all those cases which we think can’t occur (or label them as “edge cases”), which anyways still occur and make your code go crazy!

Note: I will be working with Kotlin here, but the concept can be implemented in other object oriented programming languages as well.

Bind the unbounded

So now we know, problem is ambiguity. Now, one might ask, where this ambiguity exists in my code? And this is where I point (with a 😏) towards — primitive data types! With honorable mentions for String and Int. Let me explain.

Say you developed a cool social networking app. You will definitely need a class to represent a person’s profile. Let’s name this very basic data class as Person and define it as:

Person data class

Looks good, right? We will always have a non-null id and name, what else could we ask for? You are confident that this class cannot cause any errors.

The Hammer drops 💥

Then one day you get a bug report. You check logs, debug reports and found that somehow, at View Profile screen, a weird Person object is being passed:

Loki is that you?!

Somehow the id of a Person is going blank! Rendering a profile screen empty! Or maybe crashing it altogether by throwing an IllegalArgumentException (thanks to that other dev who thought of all the edge cases beforehand and decided to throw an exception in this case 🤦🏻‍♂️)

You roll up your sleeves and add a helper method to verify if it contains valid user info:

…and use it every time before accessing any Person :

Decision making in Presenter or Viewmodel

Nice! But again, ask yourself this:

Is my code completely unambiguous?

You may get a sounding “Yes” reply, but think from other dev’s perspective? A new coder who joined? They can still miss out on the fact that a Person object can be invalid too! Which might lead to another crash another day 🤷🏻‍♂️

Problem is our code isn’t able to express itself clearly. Sure we can add comments to smooth-en out this, but its still not a sure-shot. And like we discussed earlier, code should be able to express itself & its state. This will reduce its misuse by a significant margin!

Alright, enough with the “jibber-jabber”, how to fix this??

Simple, use bounded types instead of unbounded types to hold values. Primitive data types are mostly unbounded, which correlates to their ambiguity and hence more chances of bugs.

Seal those edges

Instead of using plain String let’s make use of a bounded type — UserId based on Kotlin’s sealed classes.

followed by editing of Person data class to:

Using a custom sealed class for UserId ensures its value cannot be accessed directly. And the responsibility of safely-executing its value is now more strongly enforced at the point where it is going to be accessed:

Let’s summarize the benefits of this approach:

  • reduces the possible outcomes to literally 2 — valid or invalid
  • user id can now be accessed only when it is non-empty (guaranteed)
  • we can add more conditions to qualify a string as valid user id, for example if it matches a regex!
  • we can overload UserId.from(str: String) to make UserId from other primitive data types as well, like UserId.from(num: Int)
  • the code is now self expressive 🤩

I agree that crashes can still happen specially if someone tries to explicitly cast an Invalid userId to valid, but casting is a risky step anyways. Thus, now this is much more forced on our hands than before & is more inherent. And that is exactly what we want! To write code in such a way that writing error-free code becomes trivial, and that is precisely what we achieved here.

Similarly, we can use enum classes instead of constant String or Integer. Using bounded types reduces the super-set of possible states, thereby also decreases the number of cases to test and hence increases our code’s reliability.

Code in such a way that it minimizes the chance of writing bugs accidentally 🐛

As for disadvantages of this approach, it definitely requires quite an effort than declaring just a String, but by not making such efforts, all we are doing is handling just the happy flow, and leaving others (“edge cases”) to fate. Just like how Kotlin’s lateinit and non-null assertion !! requires you to handle its (limited) outcomes. If we don’t, we are just leaving out mine bombs on the ground for users to walk upon! 🧟‍♂️ 🧟‍♀️

Using primitive types in apps that require to be highly performant does makes sense, but with a large code base, where the bad readability of code can have severe ill effects, writing code that expresses its state is the wise option.

Conclusion

Let me sign-off with lessons and tips that can help you write more expressive code:

  1. Encoding purpose of variables in their names is a common symptom of non-expressive code.
  2. Create bounded types for whenever a variable denotes something more than just a value.
  3. Limit use of primitive data types for storing user-inputs and pure values.
  4. Prefer method overloading instead of using if/else inside a method on an input.
  5. Give preference to Kotlin’s sealed and enum classes.

For reference have a look at this inspiring talk too.

If you made it till here, then congrats! (to me of-course 😅). Do let me know what you think of this in the comments. Clap if you learned something new! 👏 👏 👏

Hit me up on Twitter for any other queries. Until next one 👋

--

--