It Started Out With a K.I.S.S.
You may be familiar with the phrase: “Keep it simple, stupid.”
Software development is a field centred around deliverables and value.
There are rarely rewards for the most ingenious solution — save perhaps a wholesome, academic pat-on-the-back.
The size of encountered problems and their required solutions is continuing to grow. This is a by-product of the human desire to strive for bigger and better. Often the solution is so vast that a single skilled worker cannot produce it. Such endeavours earn the title of large software projects.
Once the task at hand is too much for a single entity, the project’s design process must span the unavoidable hardware gap present between distinct human beings. Here, the majority of code-spewing aficionados perform much worse than expected. Obviously this issue stems from humanity’s concept transmission protocol being neither lossless nor reliable.
When utilising the increased throughput of a multi-person team, code evaluation becomes subjective. A member’s software module that ticks all the objective boxes: efficiency, functionality, reliability, and using-spaces-not-tabs, may cause bottlenecks within the development pipeline if any of the rest of the group cannot understand completely what the unit does.
It is for this reason that we are often reminded to keep software components simple. Is this as trivial as the description implies? In the rest of this article, I will investigate whether simplicity is a concept that can be agreed upon. Ironically, simplicity appears to be yet another concept sometimes lost-in-translation and misunderstood during software design or evaluation.
Simple: How do?
What does simplicity mean in the context of software development? A common interpretation is to consider the plethora of programming languages available to construct a solution from.
Here , is a diagram detailing the perceived complexity, measured as the number of distinct ‘concepts’ present within several language’s design. It suggests that the complexity of C++ is far higher than that of the ‘competing’ languages: Ruby and CoffeeScript — due to the size of its feature-set.
I agree that C++ has a reputation for being complicated in nature, with its standards-commitee forever dreaming up new ways for you to blow your foot off . Yet, I do not agree that this method of judging complexity is reliable.
As a counter-example, consider the feature set of the C programming language.
C has by far the fewest language features of today’s widely-used programming languages, but would anyone go as far as saying that it is a shining beacon of simplicity? Should teams chasing concept-clarity write upcoming features solely in a systems language from 1969?
I am arguing neither for or against the C programming language, as I have experienced it as both “simplistically elegant” and “overly cumbersome” across past and current projects.
Instead, I argue that the complexity introduced to any implementations of a given feature stems from the amount of book-keeping that anyone understanding a single module must do: The quantity of interleaving components that must be understood and remembered before contributors can describe the behaviour of the system as a whole.
Enough Space to Swing a Function Call.
Paul Graham presented the idea of using the count of distinct runtime components requiring consideration, as a metric for code size, in his essay: “Succinctness is Power” . He argues, championing his beloved LISP over Python, that the latter’s quest for explicitly readability does not necessarily produce a software component that is easier to understand.
“What readability-per-line does mean, to the user encountering the language for the first time, is that source code will look unthreatening. […] It’s isomorphic to the very successful technique of letting people pay in instalments. Instead of frightening them with a high upfront price, you tell them the low monthly payment.”
This quote is a slight sidetrack from the point. It is being used for the purpose of claiming that one language is superior to another, something this article does not attempt. Yet, Graham continues and suggests:
“Instalment plans are a net lose for the buyer […] mere readability-per-line probably is for the programmer. The buyer is going to make a lot of those low, low payments; and the programmer is going to read a lot of those individually readable lines.”
Here, we can draw the conclusion that, assuming we need both reading comprehension and memory capacity for processing a line of source-code, language-concept scarcity and single-line simplicity is not necessarily proportional to the mental taxation involved when understanding a software module.
It is documented , that an average human has difficulty storing greater than “seven plus or minus two” differing items in short-term memory. With this limit in place, any block of code with a higher number of interacting sub-systems may prove difficult to understand cohesively.
If this is the case, how can modules be structured to aid comprehensibility and interpretation?
Curiosity Confused the Cat.
There is a solution to information-overload regarding the workings of a unit. Abstract away the implementation of modules via the usage of clear interfaces.
An interface is a strict set of entry points to the functionality of a module. An ideal understandable interface is one that is well-documented and a provides a suitable modelling abstraction. Given this, a programmer does not need to worry about the subcomponents that are performing any requested tasks. Instead, they must only maintain the code required to process communication to and from the module.
Blocks of code that perform a common set of tasks should be encapsulated before approaching critical levels of memory-complexity. By presenting only required interfaces to other disjoint blocks, the concept-count at any point can be contained below harmful levels.
A well documented API is not significantly harder to understand than a basic operator, as long as it known what should go in and how this maps to what comes out. Building from interfaces yields greater potential computational outcomes given a tight concept density budget.
Simplicity Does Not Mean Sacrificing Power.
The most important feature of a good interface is well-defined boundaries, its inputs and outputs. This does not mean that the work undertaken by the interface must be trivial. It is often better to have an interface performing an action that is complicated enough to require time set aside to understand, than have many interfaces perform separate parts of a larger transformation. Understanding the applications of a powerful abstraction can make a module’s intention far clearer.
A testament to this is the Haskell language’s ability to reduce many complicated computations into a simple mapping between abstract data types. Once the domain of the problem is understood, it becomes clear which functional primitives will provide a intuitive solution. Often, newcomers to the language complain that the language is difficult to comprehend. I feel that this is often due to a wish for ‘quick fix’. Many are reluctant to invest the upfront cost of studying the task’s domain — despite the fact that would provide a net-reduction to total effort.
I believe the ease of understanding the symbolic representation of a solution an unreliable indicator of true complexity.
This viewpoint is presented, relating to many programmer’s false studying economies, again in Graham’s “Succinctness is Power”:
“If you’re used to reading novels and newspaper articles, your first experience of reading a math paper can be dismaying. It could take half an hour to read a single page. And yet, I am pretty sure that the notation is not the problem, even though it may feel like it is. The math paper is hard to read because the ideas are hard. If you expressed the same ideas in prose (as mathematicians had to do before they evolved succinct notations), they wouldn’t be any easier to read, because the paper would grow to the size of a book.”
By producing interacting software components that map to the problem domain as if they were operators, a team — or even a solo programmer — can utilise a domain-specific set of functions or services tailored to their needs.
In this article I have drawn attention to the most significant factor limiting the scalability of software teams, communication of intent and interconnection of distinct contributions.
The points raised do not attempt to prescribe a catch-all solution to the problem. Instead, they attempt to highlight a common misconception when judging module complexity during review.
I have provided an example of how teams can present subcomponents to avoid code bloat for the sake of ‘readability’. Different teams will find some techniques work better than others when partitioning work between members.
Embrace the abstractions of others. By investing the time to understand their contributions, teams can efficiently divide large problems at the conceptual level.
As a by-product, less work must be undertaken in restructuring the problem and the intent of the program is more clearly presented.
We are already standing on the shoulders of interfaces and abstractions, what harm can a few more do?