Software Design Principles

Jesús Darío
Scope.ink
Published in
9 min readJul 4, 2019

It does not really matter if you are experienced or not. If you work at a small company and just started or if you work with a huge legacy product barely capable of moving things forward. Even at The Tech Companies™ you will find the lack of Software Design principles. These are just like what brand means, only about the way we take coding decisions. Decisions that may affect hundreds of millions of people. The how is just as important as the why, and we’ll try to establish some ground on why these are necessary, how your principles directly affect your customers and a few examples to get started writing your own.

Additions and new files in green, deletions and refactors in green. The more frequency the changes are, the more intense the colour. [Source: https://app.scope.ink visualization of github.com/stripe/stripe-android

The idea (reflected above) is to keep the paths as green as possible. Refactors are necessary, but they are also a reflection of technical debt in many cases, and we want to optimize the creation of features and enhancements over the daily noise of style nits, syntax rewrites and framework changes.

The Small Decisions

There are many things in our day to day that distract us from our ultimate objective: allowing the people that are behind the code to focus on functionality and added value to the end users.. The actual use of our time invested into crafting software. If you are reading this article but you are just interested in tech because of itself, for the sake of engineering regardless of cost, this is probably not your post.

Mainly we will see the following themes surrounding the actual thinking and writing of code:

  • Style: How does the code written look. Does it need semi-colons, or does it not? Does it need space before function parenthesis, or does it not? This section could include variable naming. File and directory structure could be in this section or in semantics, depending on whether you have a Software Design Manifest or not. We’ll go there later.
  • Syntax: How different pieces of the code should be written. We need an abstract class to implement this bunch of methods, and an interface, and a series of maps and factories and… {set of pompous stuff and tech jargon}. There are aspects of syntax that can be both relevant and difficult to know what’s best; Sometimes there is no perfect solution. Coding is sometimes more art than science and experience plays a huge role. We will try to categorize these aspects later on and lay out some basic philosophy to help you shape your software.
  • Semantics: What each piece of the syntax (statements, variables, function calls) should mean. We will gather in this category every aspect that ties the written code with the reality backing it, the user experience. For example Log In is a semantic term that we easily relate to the greeting between a user and an app where they have been before. Cart is the semantic term that describes the object or the instance or the database entry where a collection of items that is about to be bought or dispatched lies.
  • Product: How the decisions we take affect the user experience. Tiny decisions that may seem to belong to syntax or semantics may affect the actual user experience. For example: If we have a piece of code that iterates over a list of elements, whether some elements can be null or not, may affect many different parts of the UX: order of the elements, time of response, how other parts of the code should handle it, whether they arise UI bugs or not (more likely they do), …

As you can easily relate, we want to keep discussions short, and focus on creating things rather than have religious wars all over our workplace. So ideally we will restrict work conversations to the product and its design. The remaining aspects are valuable and important, they deserve to be well thought. But they should be thought only once.

As creator of Scope 🔬 the aim of this article will be to promote and measure the productivity of conversations that revolve around product development. This means to enhance and focus on creative work, rather on mechanic, accessory aspects. By reading until the end I hope you have a better idea on what’s a meaningful conversation and what’s just noise.

The Big Decisions

These are the ones that will completely determine the way things are done, regardless of whether we like them or not and whose changes affect drastically our day to day. These relate to the technologies we use (programming languages, environments that are required, tooling) or the targets we have (platforms to distribute, customer range). These decisions should be based on both product and developer experience.

Style, Semantics and Syntax should never condition the ultimate decision, as they may change from context to context, but will be only anecdotal in their nature. As most seasoned developers know language, stack or tooling, this shouldn’t count towards qualifying you as a Great Engineer. For instance, changing technology stacks can be painful at the beginning but it is a very symmetric process always. If you know how to code in Language X, you will know how to code in Language Y. Experience, communication skills, care about people and product and people are qualities that count towards being a Great Engineer however they are seldom measured or taken into account.

There is one big thing that can make a big difference, though. The set of Design Principles that are implicit within each community. Functional-friendly programming languages will bring their own goodies, as other framework-heavy environments will drag your process as you lose the conveniences attached in your comfort zone.

As we get into design principles, and now that you can differentiate between small (daily) and big (one time) decisions, let’s get started with the bigger frame.

The foundations

Just as the Maslow Pyramid, any product company should accomplish the following basics in order:

So Code That Works™ is of the most important. We surely will need the rest if we can focusing on the higher grounds. And this should be the first line of your Design Policy.

Once you are doing something meaningful, your should really improve the way if you want to grow as a company, and for that there are few resources better than Joel Test’s: 12 Steps To Better Code. Really, it never gets old.

We are also going to categorize all the software our organization writes in three blocks:

  1. Business logic: The lines of code that changes the world as we know it.
  2. Application Programming Interfaces (APIs!): The lines of code that are made to be solely consumed by other code, business logic or interfaces themselves.
  3. Infrastructure: The software we use to run our software. They may have their own business logic, but whose objective is to be the infrastructure for our company code.

After all that you should really take a look into the collaboration process. We talked long and deep over how should we work with one another to reduce bugs, improve productivity, awareness, onboard engineers… If you haven’t done so, you’ll probably like to read How To Measure Creative Work.

A few notes on the infrastructure that you can use, does not matter whether you are big or small:

  • Automate your style. Use a tool like prettier to kill style talk in peer reviews. Style is just a preference. Talking about it adds little value to the conversation.
  • Enforce certain syntax to a certain level. For this you can either follow a framework (like Ruby on Rails), use typed languages or choose strategies (all this things should belong to a Software Design Manifest) that disincentives pattern proliferation, unless they are really needed.
  • Have a 1:1 relationship between your software and your product. Dissonance is lethal. Semantics here is everything to describe bugs, submit fixes, understanding the user and communicating in general. Do not host concepts in your code that do not exist in reality as long as you can (this can be more challenging that it looks so far) and do never change the meaning of a term inside the code-base, or developers will get lost, create bugs and create chaos.

The most important thing of this article is that you don’t have to follow blindly what is described here. On the contrary, the objective of this writing is to get you shape your own philosophy, and build a manifest where everyone can read and understand why things are done in a certain way. And, if it needs to change, that this change is indeed visible by all your peers.

In Scope 🔬 the team points normally to React Design Principles as a source of inspiration. Not like we need to do things exactly how they are there, but it is helpful to see them and grow up as professionals, adopting what works for each of our circumstances.

The Software Design Manifest

We the team have created this document to help you decide how are you going to shape your source, separate your components and decide which can do what and what teams should implement support for which functionality when. It will describe how your development philosophy is like. While we are excited to see all kinds of code contributions, we should avoid paths that violates one or more of these principles.

User experience is first. Software is just a means to an end.

Immutable vs mutable. When you are writing code, using immutable variables ensures you that no one can alter them, nor you can forget or hide a change in their state. Not even multiple threads or race conditions. Mutability induces to error and does not actually add syntax or semantic value. Not even fewer lines of code if you use wisely….

Composition. Functions are in every single language. They are (most of the time) hyper-predictable input-output machines –specially in typed languages–. You can aggregate functions to compose more complex logic, or import classes from the class you are using to accomplish different objectives. Inheritance is a form of implicit functionality, that after the first statement we know why we should avoid. Functions do not work so well with tateful entities (those that may change in real time or whose input/output we don’t control) that’s a good moment to create some abstractions to hide the complex details.

Avoid creating abstractions until the number of times that you use a pattern is going to be exponentially higher than the times you have written it so far. Abstract classes, inheritance, overloading… are most of the time empty shell concepts. Try to write code that is near to the reality you are manipulating. Data structs are real, use them as inputs to functions to make changes in the world. States are a good example of things that deserve encapsulation into a class.

Do not swallow errors. Errors should be almost thrown to the upper-most reasonable layer. Users appreciate intuitive design, and this comes with an interface that is honest and predictable. If you fail, explain so (if you can).

Don’t be too specific while reviewing. This is a very good principles for reviews. A minimal performance improvement or a style rewrite do not add value to the review. Product-related comments do. Engineers need time to grow and have to welcome those kind of comments, but they should not be a stopper for a pull request to be merged.

Explicit beats implicit every time. Configuration over convention wins because what you read explains what is going to happen. Implicit relations means that you need things other than the source you are currently reading to understand what is going to happen. Sometimes is terribly difficult to find the relationship and that can burn. There are cases where convention is great (they have even been laid out as examples in this very article), but unless you have a very good reason, you should avoid invisible relationships.

This is a live document, which I expect to grow with readers input as well as my own experience allows me to improve and filter down values that work for more companies and contexts. These lessons may be very abstract, but the ideas are to work in many different circumstances and allow for creativity and velocity.

Hope this is useful to you.

· Jesús Darío · 🐦 Twitter · Author of 🔬 Scope · 🏡 Yeti Smart Home

--

--