Make your code reveal its usage

A small and simple example that shows one of the benefits of having domain objects.

Lets assume we need to make a request for some kind of a token and the flow for doing so goes like this:

  1. Validate an email address
  2. With the validated address validate a password
  3. With both the validated values request for the token

The not so revealing way

fun validateEmailAddress(value: String) {
// does some validation
}
fun validatePassword(validEmailAddress: String, password: String) {
// does some validation
}
fun requestToken(validEmailAddress: String, validPassword: String): String {
return "some kind of token"
}

Q: Can the consumer of this API, without knowing the aforementioned flow, figure it out just by looking at the functions’ signatures?

A: I guess she could if the parameters’ names were like that. If not she needs to read the bodies of those functions to see that there is some kind of order that needs to be followed.

Q: Can the creator of this code be 100% certain that the functions will be used as expected and the passed parameters will be valid? For example, will requestToken always be called last and with valid addresses and passwords?

A: No! There is nothing that can guarantee that so, just to be safe, we check in every function that the provided values are valid and make the code flexible enough that, for example, each function could be used on its own. That will, potentially, lead to code duplication or unnecessary abstractions.

Q: What about errors and invalid values? Can the consumer of this API predict the code’s behavior on erroneous inputs without reading a documentation?

A: No. Just no.

The revealing way

First lets enrich our API with some, much needed, domain objects:

sealed class EmailAddress
data class ValidEmailAddress(val value: String) : EmailAddress()
data class InvalidEmailAddress(val value: String, val error: String): EmailAddress()
sealed class Password
data class ValidPassword(val value: String) : Password()
data class InvalidPassword(val value: String, val error: String): Password()
data class Token(val value: String)

Having those constructs we can change the functions and make them reveal both the order they can be used and their behavior:

fun validateEmailAddress(value: String): EmailAddress {
// does some validation and
// if the validation fails it returns InvalidEmailAddress
// otherwise a ValidEmailAddress
}
fun validatePassword(emailAddress: ValidEmailAddress, password: String): Password {
// does some validation against the valid email address
// if the validation fails it returns an InvalidPassword
// otherwise a ValidPassword
}
fun requestToken(emailAddress: ValidEmailAddress, password: ValidPassword): Token {
// having only valid values makes the code simple
// just make the request and return the token
return Token("some kind of token")
}

So lets pretend that we are the consumer of this API and all we have is the code.

Our main goal is to request for a token. By just looking at the functions’ signatures we see that there is a requestToken. Nice! Our task is half way done! What do we need for calling this function? A valid email address and a valid password. Ok. How do we get one of each?

Looking again at the signatures we see validateEmailAddress and validatePassword that return an EmailAddress and Password respectively. Lets check what those are. These are abstractions and each of them has been extended to a valid and an invalid state. The invalid one carries the error that occurred too! So, back to the functions.

We see that validating a password needs to be fed with a valid email address so we first need to call validateEmailAddress, then validatePassword and finally requestToken.

That’s it. We didn’t read the code of the functions and we didn’t have to worry about our inputs.

One thought on “Make your code reveal its usage

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s