Skip to content

More on declarative code. And simplifying APIs.

In the previous post, I discussed, briefly, an example of a very common pattern in mobile development, and how thinking in terms of declarative programming makes the code more readable and flexible.

Now it is time to discuss another example. I chose this particular example because it actually allows us to discuss three different concepts. So, onwards!

The issue

Some time ago, in a previous life, I was working on an integration with an OVP that exposed in its interface a method with a signature very similar to the following (some names and identifying details have been changed to protect the innocent):

Even though Refactoring basically provides the same example to justify Introduce Parameter Object, this code went into production.

Obviously, two weeks after release, we noticed that we needed to add some extra parameters to support pagination. The signature of the previous method, then, turned into something like this:

What is wrong with that method?

Well, as usual, when it comes to coding, there is no right and wrong, there are only tradeoffs. And opinions. And opinions about tradeoffs.

But, in this case, there is something not very elegant in the original method’s signature.

Too many parameters

Three parameters are too many. Signatures as long as that one make methods more difficult to read. The signature is long, and requires a certain extra effort to understand. Cognitive load all over the place.

But what’s more important, this method is not easy to consume.

Imagine you are writing some code that needs to use this method. Do you remember its parameters? Do you remember the order of those parameters? Which one is the startTime and which one is the endTime? That will not be a problem in languages that support named parameters, like Swift, but it will in others, like Java. You can also argue that modern IDEs will give you code hints, but the fact is that it is not impossible to mess up a call to a method that exposes too many parameters.

Fragile

Life sucks, and life sucks in particular if you are a software developer. We live in a world of constant mutability: requirements change, we have to incorporate new features to our codebases constantly, we need to fix our own mistakes. The point is: code needs to change, all the time. In particular, code that needs to change once, is very likely to change more times later on.

In this case, something as simple as adding support for pagination requires a change in the method signature, which is going to require a change in all the unit tests covering it, and an update to all the calls to that method.

Imperative, not declarative. And leaky.

The original signature of the method is imperative. We provide, one by one, all the details about what we want that method to do, and how we want it to do it. Which, in a way, is also a symptom of a leaky abstraction (clients of that method kind of know about its implementation details, by means of knowing every single parameter it needs).

But, back to the declarative vs imperative argument, we should aim for providing high-level abstraction of the expectation we have. Which is easier to do if our own APIs provide high-level abstractions of the data they need.

Introduce Parameter Object

In this case, the simplest solution is let Refactoring guide us and introduce a parameter object.

What does our API need? What does our method expect, in order to be able to provide what we want? Just a set of constraints to be applied to the list of programs it will fetch.

Then, that’s the abstraction. Let’s declare an object, and call it, for example ProgramConstraints. Let’s make it also an immutable object, keeping an eye on the amount of parameters it needs to be provided through the constructor in case we need also an specific object to build it (an implementation of the Builder Pattern):

Now, check the signature of our method:

Does it solve the issues enumerated earlier in the post?

  • The code is declarative now. We just tell this method what we expect from it, without providing the low level details.
  • The API is more robust. If we need to add extra parameters, we don’t need to change neither this method’s signature, nor the code that calls it.
  • We are not leaking the abstraction anymore. Clients of the API do not need to be aware about implementation-specific details.
  • As an extra bonus point, the code is more modular now. And it becomes easier to test also. If we change the declaration of ProgramConstraints to an interface, we can pass any implementation of it to our API, including mocks or stubs.

Final words

The code that, previously, was not very readable, due to the amount of parameters, now tells a simple story. This method fetches programs, according to a set of constraints. The code is painfully obvious. The way good code should be.

Note: Extra points would be awarded for getting rid of the Primitive Obsession

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *