How spaghetti code finds its way back to our codebase - intro

Andr谩s T贸th - May 4 '20 - - Dev Community

Around 8 years ago when we first started reading Clean Code by Uncle Bob at work I remember having fighting with the C++ devs in our team about giving it a try or maybe writing their own unit tests: "You are not going to convince us that we have to do double work! That鈥檚 why we have the QA team for!"
Fast forward to 2020 and it is hard to find a job description without the mention of clean code, the SOLID principles or unit test coverage.

Yet horrible, horrible spaghetti code finds it way back to our codebases. This little series will be about the various ways of it.

But first let's talk briefly about...

Why simplicity matters

Let's kick this off with an example from Victor Nakoryakov's Two years of functional programming article:

Example 1a:

// what does this do?
const format = R.converge(
  R.unapply(R.join(' ')),
  [
    R.always("Value"),
    R.nthArg(0),
    R.always("is not expected here. Possible variants are:"),
    R.compose(R.join(', '), R.nthArg(1))
  ]
);
Enter fullscreen mode Exit fullscreen mode

Ah, OK, yeah, hmm. Now check out this:

Example 1b:

function format(actual, expected) {
  const variants = expected.join(', ');
  return `Value ${actual} is not expected here. Possible variants are: ${variants}`;
}
Enter fullscreen mode Exit fullscreen mode

This is a contrived example, I know, and probably no one will ever write 1a, however it is also a good one to argue about trade offs.

Correctness over simplicity

While I was researching for this series I have stumbled upon the notion of correctness over simplicity. It basically teaches to be correct and complete first and then be simple. Features must work correctly, right?

A contrasting solution was provocatively titled "Worse is better", which sacrifices 100% correctness and completeness for the sake of simplicity. Sounds like madness: why would anyone want to sacrifice correctness?

Correctness over productivity

Let's compare example 1a with 1b: while the first approach may be more "correct" theoretically, it might also take 10 minutes to understand, while the second one only takes about ten seconds. And not just time matters, we usually don't talk about the fatigue associated with solving puzzles; not all 10 minutes of work count the same. So in reality a lot more energy is wasted.

At least we can say our code in 1a is so correct that we do not have bugs. But not all bugs are null reference errors (and they are usually pretty simple to fix) and there are multiple ways of catching them.

Bugs are caught by compilers, tests, customers and... your colleagues

This is the code review factor. If your code is so advanced that your colleagues do not understand it they will have two options:
1) make you rewrite using simpler solutions
1) being afraid of commenting about it because they do not want to look dumb (the ugly side effect of Impostor syndrome)

Finding bugs in code review relies on the reviewer's ability to understand the consequences of the pull request. Making it harder for the reviewer will inevitably lead to more bugs.

Therefore it is very easy to make the strong connection between simplicity and productivity. This was true before the first edition of Clean Code came out and is true ever since.

The time you spend on understanding something that could have been written simple is time wasted from product work.

Choosing simple technologies

In software engineering we should start from the product requirements and find the solutions for them and not the other way around. That means the language and technology you love might not be the best for the problem at hand. 馃挃

I know it sounds funny, but I saw frontend projects in groovy server pages just because somebody was married to the language. (Good luck for the recruiters!)

Strong guarantees, strong constraints and added complexity

There is unavoidable complexity though. There are products that need strong guarantees: writing code for a rocket or a pacemaker or an 鉁岋笍unhackable鉁岋笍 OS function have to be different than code written for an error reporting form.

For the three former examples using Rust might be an excellent choice. For the latter you should use JS + React without even using the immutable.js library.

Now let's take a glance at Rust. Rust is amazing, solving great problems with elegant solutions if you need to control memory, efficiency and threading. It also introduces many-many complicated stuff: you can't just throw parameters around, you have to learn about borrowing, copying, lifetime, mutability, differences between the 'heap' and the 'stack', and then choosing the right one for the right task. Therefore...

Writing a throwaway error reporting form in Rust is gonna be spaghetti code compared to an average JS React app regardless how skilled you are.

Simply because of the inherent complexity of the language. So if you don't need to have a refined control over any of those then choose a language and technology which is the simplest for the problem.

What is simple to you might be spaghetti to others

"Wait! But good developers understand hard stuff". That is true and why should we lower our standards? However there are many angles to this.

Most of us work in large companies with large number of devs of different backgrounds: code owned by Team A might get pull requests from Team B and Team C. Team A might have full stack devs who are only working on frontend code maximum a week per month, or it might have junior devs nervous about their own decisions.

Therefore your solutions can't rely on hardcore experience in the given technology; you have to flow with the crowd, you have to prepare the examples your colleagues will copy-paste and modify just to get their feature work done.

Choosing the language is also choosing the talent pool

The last angle I will be talking about is the talent pool you might have. The "best" language might not have enough experienced devs to work with leading to, you can guess, spaghetti code.

I was once working with a team who had their code base in Elm: the team lead had one year experience of it and everyone else just started learning it. They soon became the bottleneck for a project with a 3 month deadline. We didn't have time to learn their language and recruiters could not hire people. It soon turned ugly, people shouting at each other during meetings, and so on.

Technical decisions do not exist outside of business and social circumstances.


Thanks for reading my article!

Did I use the wrong phrase? Did I make a typo?

I am not a native English speaker, any help regarding the right phrase, the right language, style is super welcome!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .