Lamentations On Software Development Principles

These are the thoughts and opinions of a software developer, hopefully, to gain understanding or, at the very least, clarity.

Dev 1: "Don't you think that this method is similar to the other, I think we should abstract some parts of it..."
Dev 2: "I don't think that's necessary, we only have those two methods"
Dev 1: "Yeah...but can we should at least try to follow the DRY principle"
Dev 2: "But we don't really need the code to be abstracted..."


This is one of the many conversations I've had with brilliant and experienced engineers, and over the years, I've been on both sides of the conversation. This article doesn't really define a "right" way to do things, but rather, it goes through some common principles that we as engineers try to practice daily and my opinion about them.

Before going further, I should mention that the contents of this article aren't written to be controversial, but rather, to present a healthy environment where we can have these conversations with no judgment.

"Opinions are the representations of an individual's perceived existence" - unknown

DRY: Don't Repeat Yourself



"As software developers, we get so caught up on doing things right and clean the first time, that we fail to realize that it's not code we're delivering, it's a solution to a problem"



This is the first principle we will look into, mainly because it is a common conversation that we have in this industry. This principle, formulated by Andrew Hunt and David Thomas in their book The Pragmatic Programmer, states that “every piece of knowledge must have a single, unambiguous, authoritative representation within a system.". In my own words, it means that when building systems, certain repeatable behaviors/functions should stem from one single piece of code. Failing to observe this principle leads to another solution termed "WET", which translates to "Write Everything Twice".

I know, I know, you're probably thinking "This is an obvious principle dummy!" but hear me out, if we really look into this principle, some questions arise:

  • Should we consider pieces of code that look alike as repetitions?
  • Should we consider pieces of code that behave the same way as repetitions?
  • When should we actually make the abstraction?
  • How do we decide that the abstraction isn't just another repetition (Gotcha!!!)

And there are a lot more questions that arise in your mind also. After a few years of answering these questions in my own mind, I have a few opinions that I have come to see as advantageous when working in a team.

First, and the easiest way to identify a needed abstraction is to listen to the comments of your teammates, if two or more of your team members (depending on your team size) make comments on your code, probably during a code review, then it's time to have that discussion with them on how to approach creating that abstraction. The reason I say two or more is because the opinion of one team member is what it is, an opinion and nothing more, once two members have the same opinion on a topic it transforms into a common view which holds more gravity than the opinion of a single team member.

Another opinion of mine is to avoid hasty abstractions. Let me walk you through a scenario, you are working on a project and you notice that there is some piece of code that has been repeated, so you decide to abstract that piece:

function (param) {
  // insert repeatable function here
}

And all is well with the world. Then some days later, a teammate informs you that they need to that function to have another behavior that is similar, but not the same, so you update the function:

function (param) {
 if (new behavior) {
    // insert new repeatable behaviour
  } else {
    // insert repeatable function here
  }
}

And this can go on for quite a while until you are left with a very large piece of code that no one wants to touch, so you tag it as "legacy". This has always been a common trend/pattern for me and other developers I've talked with. 

One of the ways to avoid this is to always try to build enough use cases in your codebase for your abstraction to exist, personally, I look for three or more use cases in my codebase and then build my abstraction around those use cases.

YAGNI: You Aren't Gonna Need It



"There is a thin line between laziness and efficiency" - someone smart



Ahh yes!!! This is my favorite principle, mainly because I'm a lazy, NO...efficient developer. Most times as developers, we have been trained to think critically, consider every edge case/scenario and this causes us to think about future scenarios (sometimes in the distant future).

"Do you really need kubernetes?"
"Is it really important to rebuild that state management library from scratch?"
"Do we really need a microservice?"
"Do I really need a grilfriend, Mum?"

I kind of drifted off with the last question, but you get the point, sometimes, we think about a future that may never exist and this causes us to build up unnecessary complexities in our systems (there's a principle for this too).

One of the ways I think we can avoid this is to always implement our solutions by asking ourselves this question: Is this the simplest thing that could possibly work? This question can go a long way in helping us avoid these complexities in our applications.

Remember, when you're writing a piece of code and you're only justification at that point in time for writing it is that you're going to need it sometime in the future, then STOP. If you do need it in the future, feel free to text every time teammate and say, "I told you so!!!!", then build it at that time, when it is truly needed.


KISS: Keep It Simple, Stupid

This principle was first mentioned by Kelly Johnson, and it simply states that most systems work best if they are kept simple rather than making them complex; therefore, simplicity should be a key goal in design and unnecessary complexity should be avoided.

As far as opinions go, this principle has always been the hardest for me to follow, mainly because, I think I'm part of an ecosystem that prides itself in presenting solutions that may seem simple at first, but in the end produces unnecessary complexities (Yeah, I'm looking at you Redux-saga) and so, my thought patterns have always lead me to provide those kinds of solutions.

As it turns out, from experience, the simplest solutions are usually those that are kept in plain sight and are seemingly obvious after they have been brought to life. Here is a rule of thumb that may or may not work for you, if you present a solution to a problem and your teammates are rather unimpressed by it, stating that it's "obvious" then you have rightly followed this principle. Now, I'm not saying that every solution is going to be simple, but most likely than not, they will be.

As it turns out, I'm still learning to follow this principle, as I still on occasions, unintentionally add complexities to solutions I conjure up. Thankfully we have a great CTO who, like me, values this principle and quite more often tries to educate us about it.

If you have an opinion and also want to share your thoughts on some of these topics, please feel free to make a comment below.

Looking for a new challenge? Join Our Team

Like 12 likes
Sam Omole
Share:

Join the conversation

This will be shown public
All comments are moderated

Comments

Caleb Grimah
June 11th, 2020
Great article, was very much a refreshing read.

Denis Pshenov
June 16th, 2020
Great point about CTO. I find it's very important when these beliefs are shared from the top of the organization so that the whole team is on the same page. Although, in my experience, I found it's more difficult and more often required to enforce quality team-wide rather than reduce over-engineering (unless you have a team of perfectionist rockstars!).

As you get more years under your belt you start to get better at balancing complexity. In my opinion, one of the best tools for this is modularization. When your code is modularized you are already halfway to creating abstractions. Modularization comes in many forms and shapes – small functions, single responsibility classes, components, feature folders, microservices, packages, etc. The sooner you start taking this approach of modular code the easier it will be to add complexity later.

Cheers!

Get our stories delivered

From us to your inbox weekly.