Code to extend

Kristaps Vītoliņš
ITNEXT
Published in
5 min readNov 24, 2019

--

What is great in software versus hardware? It is easy to change, maintain and understand.

Software is a set of instructions, data or programs used to operate computers and execute specific tasks.

Word “Software” contains soft, which by definition should be

Easy to mould, cut, compress, or fold; not hard or firm to the touch.

All this is true, but

Any fool can write code that a computer can understand. Good programmers write code that humans can understand. — Martin Fowler

The computer doesn’t give a Flying Flamingo how your code is structured, has it been reviewed, does it contain nonsense comments. Compiler boils code down to instruction and computer executes, jobs done. Everybody is happy, except the next developer who will have to make changes. And take full responsibility if his changes break something.

Ladies and gentlemen, here is our story, how to make your code readable and most important extendable. I invited my friend to a podcast, his name is Oļegs Ganzins and he has a huge experience in development. We talked about if statements in code.

If statements are a unique piece of art. And there is no way around them, for example, to compare two numbers. But when they are misused or overused, problems arise as for example “If Christmas trees”.

What is an if Christmas tree. It is when you take nested if statement code and turn the code ninety degrees and suddenly code turns into the shape of the tree.

The first problem, context switching. In the provided example, of course, it is easy to follow the code, and the switch of the context isn’t hurtful. But in real life, there is a whole lot of logic written, before the next statement kicks in. And when the next if statement hits, the context inside brains has to be switched from “turn” to “around”.

The second problem, code extension. Let us add new functionality, to turn the ship around slowly. One more if and pass the parameter right down the rabbit hole.

The simple extension makes already this nasty. All kinds of red flags in head pops up, how to test this, how many unit tests are needed to jump into each if branch. What will happen when “t” is equal to “super slow”.

The third problem, the level of responsibility. Method “turn” is not just complex by design but has more than one responsibility.

The method knows how to turn it fast and how to turn it slowly. This builds to point when changing one thing another can be affected by accident. These bugs usually are called features.

How to make code extendable and keep in mind that it should do one thing. Look into SOLID principles and by just exploring each of them, patterns and ways how to create neat constructs will come in daily life.

We concentrated on S for “Single responsibility principle” does one thing and does it well and O for “Open–closed principle” open for extensions and closed for modification.

At the end of the podcast, I gave Oleg a challenge. Write code with IF statements. And then do the same without them. The use case is a payment system, which based on gateway processes the payment. Where the gateway can be Google, Facebook, etc.

Let us look at how it turned out with purely with IFs. Github.

We have the usual suspect in front of our eyes. But I have to say, even with IFs the code still looks clean and readable. I was awaiting something nastier. The issue with this is, whenever there is a new gateway, BillingService has to be changed. Thus we are breaching the O in SOLID. As for S in SOLID, class or code should have only one reason to change. But we can count multiple reasons why BillingService can change. New payment gateway introduced, a new implementation of an existing payment processor.

Let us look at code what happened when he removed the IF statements for the same service. GitHub.

They are gone and code looks cleaner and upon new gateway, this code still stays untouched. And thus open for extensions close for modification and there is only one reason to change.

What are the changes Oleg did? He removed the responsibility from BillingService to create a payment processor and knowledge about how many gateways there are.

All the processors and gateways are defined in the “PaymentProcessorRepository”. This still somewhat breaks the O in SOLID, but at least we are just extending the know-how of repository, not changing it.

He went one step further and implemented the same if wall with a Guice injection. Let us look at how the same BillingService looks with the Guice injection framework. GitHub

The abstraction level just skyrocketed in heaven :). Abstraction over implementation is a really good way to ensure future-proofing of the solution. And this applies not only in the software world but in the real world as well. For example, a car without a clutch mechanism, shifting gears would be somewhat impossible.

Where is the implementation of BillingService? Here it is. GitHub.

We still see that there is a PaymentProcessorRepository, but there is a significant change to it. But before we jump to it, let us look at Guice Module PaymentProcessorModule. This is the place where welding happens, the seams are strong but easy to change or replace. Basically, on any new processor, we register it in the module. Compact, neat and makes sense as this serves as code as configuration. Keep in mind class discovery can be made automatically, but then you lose the high overview of what binds to what and how.

Changes to PaymentProcessorRepository are astonishing. It has zero knowledge of where and how processors are created and how many gateways are supported. It just gets all of the processors by injection and serves them when requested. Whenever a new gateway is created or implementation of the processor is swapped, zero changes are needed for the repository.

Thus achieving the S and O in SOLID. Each piece of code does one thing. And the Payment system is closed for changes but open for extensions.

This kind of approach opens a lot of functionality, like swapping the implementation, even in runtime. Unit testing gets a hell lot of easier, as it actually tests small units.

What makes this approach really shine is that business logic can be tested separately from the engine. “DefaultBillingService” and “PaymentProcessorRepository” is a part of an engine, but Payment Processors is the business logic. Why this is important. Usually, the engine as such is created once and it just works. Where business evolves all the time. And the main goal is to not change the engine because of the business. Thus, avoiding it is not a bug, it is a feature.

This is our story on how to handle IF statements. It is impossible not to use them at all, but it is possible to not overuse them.

We suggest reading about SOLID principles as it will lead to a lot of interesting approaches and patterns. And remember, pushing yourself to look and implement patterns takes time and thinking. This increases the initial cost of your solution, but in the long term, it does payback. On another hand, doing fast and crappy code initially is cheap but in the longterm will never payback and will cost more and more to add new functionality.

GitHub
With IF statements
Without IF statements
Without IF statements implemented with Guice.

--

--

I’m a Software engineer, dad, and husband. Innovation as a service.