Share
Software Design principles: SOLID and FLUID


Required skills: Software Design Principles

You will learn: Flexible design principle Code locality principle Program state unambiguity principle Program intuitiveness principle Software dependability principle


All of us know how to create an application, web site, script, whatever. Most of us heard about good design principles, know popular design patterns. Some of us could create scalable, powerful applications, which can solve truly complex tasks. Who of us like maintaining old big applications with historical burden?

I want to share main principles that allow to make maintenance if not exactly enjoyable, but at least a predictable task.

Maintainability principles:

  • Flexible — application should support reasonable deviation from original design
  • Locality — small change should affect only its neighborhood. Problem in one component should not impact whole universe
  • Unambiguous — you should be able to say what happened and why
  • Intuitive — the most evident way to do something must be correct one
  • Dependable— you should be able to rely on other components to do their task


You are a great programmer. You have created a perfect application. You have tuned it so that it works like clock: tick-tock-tick-tock. You did your job, time passes, and onto the scene comes junior programmer, with his small task, an irrelevant JIRA ticket he needs to do. He opens the code, finds a place, makes a change and… suddenly, precise mechanism of the application becomes out of tune. Visibly it still works, but deeply inside something has gone wrong: tick-tock-tick-tuck-tick-truck. Will this eager but yet unskilled programmer be able to notice the anomaly? Will he be able to fix it again, like you did? I am afraid — no.


Flexible

Writing a tool, a framework or an application you normally have some idea in your head how it will be used. It is not uncommon, though, that it will be used later in completely different aspect, completely different approach.

When someone adds a feature the application was not supposed to support, it could still work. When small “unpredicted” changes do not ruin the application, we call the application flexible or extendable. The measure how far the the application could be changed, preserving its structure, is the flexibility of the design.

An analogy could be found in physics - stable equilibrium. Whenever you displace the ball from its position in a small hollow, it tends to returns to its original position. Unless you move it too far — in that case it will simply go over the edge and who knows where it will find itself in the end.

Flexibility allows to preserve good design

Profit of this design principle: application becomes more stable and relayble, since new features won't easy affect its architecture.


Locality

Ideally, any small change should be possible to implement touching only limited count of components, classes etc. Impact of such small change should be easily understood from its direct dependencies. Its antipode, non-local design, means that modifying one component you never know what other components will be impacted.

Well known principles like Single responsibility principle, Loosely coupling or High cohesion - all aim to reach locality of the code.

Nature (and physics) again proposes us full analogy  — locality of interaction.


Old story, but still true

Profit of this design principle: you could change pieces of code analysing only its direct usages, without necessity to understand all nuiances of the application. In other words, reduces the cost of programmers that could support/develop the application (e.g. juniors instead of middles, middles instead of seniors).


Unambiguous

Bugs happen. What is worse, sometimes they happen in most unsuitable time and have very unclear origins. To fix the bug you need to understand what happened and why. Sometimes it could be done by looking into the code (for hours), sometimes — looking does not help. Sometimes all you have is a log file. Sometimes you have none, but a messy user report. Sometimes… Trust me, a day will come when you will sit and look at code and say: “How?! How could it have happened? I do not understand.“. And you will wish you had better logs. Better traceability of your code. You need to understand how exactly the program reached this exact point, what branches were executed and why.

Log files or stack trace - it is a projection of true application state onto usually lower-dimension space of logs or other application artifacts. By state ambiguity I call the situation when from artifacts we cannot reconstruct full state the application had at that moment.

Ambiguity is usually caused by having states that describe several orthogonal substates (i.e. independant substates that could and should have been separated) and state diagram having cycles.

If application does not follow this principle, you will frequently hear the phrase "we need to add some logs and deploy into production" when next bug occured.

I pity programmers who investigate similar stack traces without debugger

Profit of this design principle: easy to investigate and resolve all kinds of bugs. Reduces time-to-fix limited by your application release cycle, gives predictability of bug fixing.

 

Intuitive

Good application can live for five, ten, twenty and more years. Many teams could put a hand on it, with their own ideas and concepts of good design. Please, whenever you design your framework, make sure that simplest way to use it is the correct way. People should not explore whole code-base and understand all the flows in order to implement a small feature. They just need to do it intuitively. Whenever the intuitive expectation of program proves true, a harmony exists :)

If you need to do something contra-intuitive — for example, performance or memory optimization, that heavily rely on hidden knowledge (e.g. memory alignment or cache line size) — please, please add comments and runtime checks or tests to ensure that your assumption still holds.

The same stands for all aspects of programming: functions, classes, tools — they must be intuitive. One could not possibly count how many hours could be saved if more programmers followed this principle.

Why?! Please, why no logging?.. 😢

Profit of this design principle: all users of intuitive API/frameworks etc will just use it and be happy about it. Unintuitive programs, however, will case a lot of negative emotions from both consumers and programmers that will maintaine the code. Many hours and bugs could be spend simply by giving functions intuitive names (i.e. names means exactly what function does).


Dependable

It is not rare when a problem in downstream component is “cured” in upstream component. Common arguments are that it is simpler that way, it is quicker, it is hard to understand what’s going on in downstream component etc — know, all of these are ways to elude the responsibility :)

You could compare it with normal life, where we have laws. From one point of view, they restrict us. However, when you start using them, when you begin to rely on laws — you reveal that they give you opportunities that were impossible otherwise. But that is true only when laws are dependable, when laws are trustworthy.

The same principles rule programming. Specifically, you should replace word “law” with a word “contract”. Each component should strictly follow a contract — only then other components will be able to rely on it.

Example: component A produces XML, used by three consumers B, C and D. If consumers do not trust A, they will need to build separate XML validation mechanisms, think about what to do with broken XMLs, and so on. And all of above will be required in three components: B, C and D. Which is triple work comparing to situation when we could simply depend on A.

Though in case component A fails to maintain contract, all B, C and D will be impacted, the problem could be mitigated by adding special defenices only to most critical component / execution paths.

Profit of this design principle: highly reduced complexity of code comparing to the bullet-proof design when each component does not trust other one.



What is next?

Most principles are useless unless you know how to control or even measure them. In next article I will tell you how one could mathematically estimate maintainability of given application, based on above principles.


Now you could say that you know about FLUID principles of software maintainability:

- Flexible design principle

Code locality principle

- Program state unambiguity principle

- Program intuitiveness principle

- Software dependability principle


Wish you that all your programs, though SOLID, were still FLUID :)

User Avatar Anton Naumenko
October 21, 2020