Simple code: Simplicity

Simplest solutions are usually the best solutions.

We as software developers work with hard problems and solve a lot of small problems every day. Solving a hard problem itself is a hard job. Though in my opinion it's not enough to solve a hard problem in any possible way but a hard problem should be solved with a simple solution. When a developer comes up with a simple solution to a hard problem then they can declare the problem solved.

First a disclaimer. Coming up with a simple solution to a hard problems is itself a very hard problem and takes a lot of time, effort and practice.

I've seen my share of "clever" solutions for hard problems and the problem with those is that usually the solution itself is so hard to understand that depending on the size of the problem it may take a developer from hours to days or even weeks to understand how that "clever" solution works. It's a rare occasion when a developer has come up with a simple solution to a hard problem. So simple that it needs to be read only once and it makes sense. I dare to say I've made a few of these solutions and I've also seen these from other developers but way less often than hard to understand solutions for hard problems.

So how does one come up with simple solutions to hard problems?
First step is to split the problem to smaller problems. How this works for me is that I try to identify smaller problem areas within the original problem. Once I have identified smaller problems I can usually find yet smaller problems to solve within those and I continue to do it until I have a plan, a set of tasks or small problems to solve. Usually I identify even more problems to solve once I start to work on them and I just add them to the list of small problems to solve. With this iterative process I can solve the problem one small piece at a time.

Splitting the problem to multiple smaller problems gives other benefits too. Each small problem can be tested individually and the big problem's tests work as acceptance tests for the whole set of small problems. Smaller problems are easier to solve and therefore the code is easier to write and when the code is easier to write it's easier to write readable code.

In a ideal situation the big problem would be solved by sequentially calling functions that solve a smaller problem within the problem space. In a way it could be thought like the solution to a problem can be solved by a function introduced in a interface. That interface is tested with a acceptance tests and it can be tested with mocks or spies to verify it calls the correct functions in correct order with correct parameters. The implementation of the interface is actually a series of function calls that each solve a portion of the problem. Each of those functions is tested with unit tests. Each of functions can be written as easily readable by keeping them small and naming the functions and things within those functions meaningfully. When a function is small and solves a single problem the solution is easy to define with immutable data structures or by avoiding mutating the variables. Also when each function works as it's own unit it's easier to isolate integration tests to those functions.

This is how simple solutions are crafted and this is how all the topics I have covered earlier are tied together to create simple, readable, verified and long lasting solutions to problems.

With all this tied up what started with a working title "My version of clean code" could also be simplified and after thinking about it more while writing these posts I decided to name the approach and conventions as "Simple code".


Simple code: Version control commits

Currently the most popular version control system is git and I'll be writing this based on git and it's functionalities and capabilities.

Git is often seen as a way to enable distributed programming i.e. multiple programmers can work on the same code repository quite easily without disturbing each others work (much). In addition to that just like other VCS's it's also a log of work but to my experience that part is often unfortunately neglected. What I will be focusing this time is the log part because I think it deserves more attention.

Why to create a meaningful log?

The git log should consist from small meaningful changesets where each commit addresses a single problem. By dividing the log to small commits it enables resilient way of working. Being resilient enables simple and fast procedures to rollbacks, reviews, tags, branching etc.

Lets say that a developer is implementing a REST API. The API needs a web layer that receives the HTTP requests, it probably has some sort of logic layer to do data transformations and validations and maybe some calculations and finally it has a data storage where the data is persisted. There are options how to record this work to the log. One option would be to implement the API and record a single commit or squash the commits before pushing the changes to remote so it would become a single commit. Another option would be to record commits every now and then while developing and finally push those commits as is to the remote repository. Yet another way would be to carefully pick what is recorded per commit in order to have a set of meaningful commits that each address a single issue.

Example of the first approach would be something like this:

Simple Code: Integration Tests

An integration test is something that tests a functionality that is dependant on an external system e.g. a database, HTTP API, or message queue.

Integration vs Unit Tests

The line is thin in my opinion. The integration part can be faked, or embedded services can be used in place of the actual integration point, and with these solutions, the interaction with the external system is bounded in the test context and the tests can be executed in isolation so they are very much like unit tests. 

Simple Code: Acceptance Tests

Acceptance tests are a great tool to verify that the application or system works as expected from end to end. Sometimes these tests can be called end-to-end tests but sometimes end-to-end tests have a different meaning. Another term to describe the same functionality is QA tests and a subset of acceptance tests is often referred to as smoke tests.

The Idea

The idea is to define input and the expected output and once the system and all its dependant services are running the whole system can be verified to work as expected. In an ideal world, the acceptance tests would be implemented based on the acceptance criteria of the use case.

Simple Code: Unit Tests

Unit tests are the developer's number one safety net. Let that sink in. This is the number one reason for writing unit tests.

Unit tests are written by developers for developers to ensure that the code works as expected and handles happy and sad paths correctly. With enough unit test coverage, the tests enable a safe environment for refactoring and rewriting code.

Simple Code: Immutability

Immutability is a special thing that in my mind deserves a short explanation and praise.
If you're familiar with functional programming you surely recognize the concept of immutability because it's a key ingredient of the paradigm. In the world of object-oriented programming, it's not as used and as easy to use approach but there are ways to incorporate immutability to parts of the code and I strongly suggest you do so too.

Quick Intro to Immutability

The basic idea of immutability is unchangeable data.