DEV Community

Cover image for Building Simple Software: Defining the "What" Before the "How"
Maisam Somji
Maisam Somji

Posted on

Building Simple Software: Defining the "What" Before the "How"

One of the most difficult challenges when it comes to software development is minimizing accidental complexity. Due to its ever changing nature, software needs to be easily consumable and loosely coupled to other parts of the system to allow new features to be added with ease at a high level of confidence. This unfortunately is not the case with most software in the enterprise world since building simple software is rather difficult.

"Simple is often erroneously mistaken for easy. "Easy" means "to be at hand", "to be approachable". "Simple" is the opposite of "complex" which means "being intertwined", "being tied together". Simple != easy". Rick Hickey (Founder of Closure)

Many design patterns have been proposed over the years to help maintain software i.e the Factory Pattern, the Singleton pattern etc. Though these have helped to provide some structure, as applications grow, so does the pain of maintaining them. A different strategy to tackle this problem is by using the declarative approach. Declarative programming requires one to step back and think about "what" exactly will be needed in order to solve a problem instead of dwelling on the details of "how" the problem will be solved.


Image of code

Over the years I have learned different strategies to break down seemingly complex software problems to relatively simple ones using a few basic concepts.(folks familiar with functional programming languages will likely find a lot of parallels as you read on).

There are three things that come immediately to mind when I think of good software. Immutability, the use of pure functions and the single responsibility principal. Immutability is defined as an object or variable that can not be modified once it is created/assigned. Once a variable is created, one can trust that its value will not change, ever. Pure functions are also related to this principal whereby given an input they will always return the same output - One can rely on the function to work as expected at runtime (More on this in a future post). Lastly, but most importantly, the single responsibility rule - making sure that in each step of solving a problem, only one thing is being dealt with. This is important because it limits how much context one must hold in their head while trying to solve the problem. We as humans are limited, so the less context we have to maintain the better.

These might seem obvious and popular principals so the question arrises - Why does software continue to become complex? I asked the same question only to realize that it was not the acknowledgment of these principals that brings about simple code, but rather the way in which they are applied. To illustrate this, let us examine how we would build a program that calculates the bowling score of a given individual(s). The key take away from this example is to learn how to depict each step of the process in a clear fashion prior to writing a single line of code. Remember, the code is the simple part.

Bowling Objectives:

  1. Add a number of players to a bowling game.
  2. Provide a name and the number of pins dropped in a frame to add the frame to that player's score
  3. Given a name, get the players' score at that point in the game

Based on these objectives, instead of writing out sentences of what I want to build, I rely on the specific contract I am trying to implement. Below are the contracts associated to the high level objectives identified above using the following structure: input------------- method name---------> output

  1. [name] --init -----> [Players]
  2. name,new_frame--add_frame--> updated_player_board
  3. name(optional) --get_score--> ScoreBoard
*Notice how the inputs and the outputs directly match up up the objects aforementioned

Now that we have the overall contracts, we need to think about what steps each of these contracts need to take to obtain the desired output. The question always being - " What input do I need to get the desired output". Below is what I arrived to when I asked the same question.

Bowling Contracts

*Validation was not part of the initial objectives but was discovered in this phase to be important enough to be included.

Notice how each preceding contract returns an output that is either used as an input for the next contract or is the output you are looking for. Additionally notice how each step has a very specific method name aiding with maintaining the single responsibility principal. Keeping true to the declarative programming paradigm, I am not too concerned about "how" the code will be implemented but rather just fixated on "what" each step needs to accomplish. I keep breaking down the contracts until they can no longer be broken down into smaller steps. For example: in order to determine if something is a strike, the least amount of information I need is the frame in question, the "how" is irrelevant.

Once we have all the contracts in place, all that remains is implementing the code. The beauty being that you no longer need to think about the bigger picture, each contract being pure, can be written in isolation and so long as the inputs match what was planned out, things will fall into place as expected. ( I have surprised myself on more than one occasion on how well this process works!)

A working example of this code can be found here. You may notice that not all of the methods appear to align with the contracts shown above, this was intentional to show how little I had to deviate from the plan once I started to write the code to get to the desired solution. An example of me solving the same problem solved while not following these principals can be found here. The difference is striking.

It takes a lot of discipline to get away from being distracted by the details of "how" a problem can be solved and really limiting yourself to only thinking about "what" the problem needs in order to get to the desired output. However, once you make the switch, problems will present themselves to be more solvable. Furthermore, the code will also naturally be understandable due to the the fact that each method would be an independent unit, like a piece of lego, easily tested, chained or nested until the desired solution is obtained.

I hope this has been helpful. I don't claim to be an expert however if this was helpful even in the slightest or if you had any follow up questions/comments, please let me know in the comments section below or find me on twitter (@Somji_).

Top comments (0)