What matters more: code performance or agility? Shall we abandon a clean code philosophy to enhance performance? Let's answer these—and many other questions—together with the PVS-Studio development team.
Is clean code harming the tech industry today? This question arises from time to, sparking debate among developers. Today, we'll dive deep into this and other burning questions of this endless discussion.
To gain a broader perspective, I asked other PVS-Studio developers what they think about clean code. I peppered my thoughts with my colleagues' quotes and some stats.
As you might have guessed from the title, this article is a follow-up to the first part. In the earlier piece, I focused on the discussion between Robert Martin and Casey Muratori—it's the most extensive content on the topic. If interested, welcome to take a look at it. However, it's optional to refer to the first part to read this article.
Let's move on from performance issues and ask a different question: what's clean code? If you and I were sitting in the same room, I bet we'd hear as many answers as there are people in the room. Everyone seems to have their own idea of clean code: some devs say that proper naming and decomposition are enough, while others insist clean code requires polymorphism and a high-level abstraction.
That's why I tend to see clean code more as a philosophy; Oleg Lisiy, our Senior C++ developer and Tech Lead, has a similar take on it:
For me, clean code is more of a philosophy. Obviously, we need to treat it wisely, as well as any approach to problem-solving.
I think we can try to figure out whether clean code has a positive or negative impact on the industry by examining the purpose of its existence. The idea behind the clean code philosophy aims at writing code in such a way that its development and maintenance don't turn into infinite tambourine-dancing sessions. This saves both effort and time for developers and lets them work more productively on their tasks.
I think every developer at least once in their career encountered code that they'd like to clean up; just stock up on cleaning chemicals and do an ultimate cleanup so that no one suffers no more. In theory, following the principles of clean code should mitigate the number of such cases.
That's why I think clean code is generally a good thing. It's every developer's duty to think about how to write functional and human-readable code. Discussions about the fact that clean code is not perfect are also fine and important. They help us figure out what we need to change in our perception of clean code to create the top-notch and best software it can be.
Let's see what our developers think of clean code:
Most stayed neutral! Moreover, their point is well-founded, and I'll try to explain this stat further.
We've talked about the clean code benefits, but there are still many "ifs and buts" we need to address.
Clean code rules are quite numerous, concerning many things like function and variable names, code comments, formatting, testing approaches, software architecture, error handling, and more. But do we really need to follow every single rule in every scenario?
To answer this question, let me give an illustrative example. Every developer, who is early in their career, writes a program that outputs the cherished "Hello World" to the console. My colleagues showed me the repository with a surprisingly elaborate way to tackle this trivial task. This is Hello World Enterprise Edition, the alternative to the simple output of a well-known message which is littered with abstractions, factories, strategies, and other OOP stuff.
Well, let's get back to the question. Is it essential to stick to all those intricate architectural principles of clean code while developing a TODO Python app? These architectural maneuvers won't streamline an app development, and they can significantly drag out the development process—and like for what, uh.
I don't think it's necessary to follow all the clean code rules for every task. Once again, I quote Oleg Lisiy, our C++ Tech Lead:
Every project has its own patterns and rules. It's not always possible to apply these rules. If something seems superfluous, it's probably unnecessary at this stage. The architect's task is to pick those patterns, techniques, and approaches to writing code that will work for the specific project.
Let's see if our developers consider clean code redundant:
Here, most developers agree that clean code rules can be redundant! This doesn't mean we should abandon the clean code principles—but let's be a bit more realistic about how we can use them.
Tests are a key part of software development, they help devs understand how well the code has been written and whether it's up to par. When do we need to write tests, though?
Robert Martin and Casey Muratori debated this topic. Uncle Bob advocates for Test Driven Development, or TDD: it's an approach that maximizes his benefits in the software development. On the other hand, Casey prefers to test specific parts of the code.
I wouldn't be so adamant on this issue. I think both of these approaches are worth looking into. First, it's important to understand what issue we need to solve, so, we can pick the right test-writing strategy.
For example, if we need to refactor some troublesome code fragments, I think it's better to write tests first, before tweaking the code. So, we don't disrupt the rest of the program operation.
However, spending valuable time on writing tests that are either always succeed or never run isn't productive. They don't find issues, so they're not as useful.
Let's see what my colleagues think about whether TDD is a more effective testing strategy:
Most people prefer to choose a testing strategy depending on the task at hand, and it seems that proponents of each approach are almost evenly split.
Another issue with tests is achieving 100% code coverage. I think this goal is often mentioned, but it's not all smooth here.
Full coverage is quite a challenge. In some code sections, it may be impossible, such as in the modules involving pseudorandom numbers—it's tough to predict their execution result.
Besides, no one can guarantee that the 100% test-covered code will be flawless. It's important to understand that while tests are necessary to automate the QA process, we, the developers, write them. So, there's a chance that devs may overlook some instances, and a user may stumble upon them.
At the same time, I don't think that aiming for full- code coverage is a curse, either; however, striving for a 100% coverage might be unrealistic. It's more practical to cover the code with tests in important places where we must avoid any errors. Plus, we'll make lives of ours and other devs easier.
I asked my colleagues about their views on the full test coverage:
Most people think it's necessary. Interestingly, fewer people take a neutral stance as compared to those who think that the full test coverage is redundant.
That's the main question this whole story is about. What's more important, writing flexible and maintainable code or saving program execution time?
If we look back at the rules Casey Muratori mentioned when he said that clean code kills performance, we'll find points that has been questioned directly or indirectly in this article too.
Is it always worth maximizing abstraction? Do features have to be straightforward and perform a single task? My answer is no, not always. However, note that we don't always need to squeeze out maximum performance of the code, either.
In some scenarios, 100% optimization isn't necessary. We can speed up the method and parallelize it as much as we want, but if it sends the network request, we'll be limited by the internet speed.
I try to take the approach where I don't have to focus on one thing. It's possible to write clean code that works well. Konstantin Kulikov, another Tech Lead on our C++ team, told me the same thing:
Everything depends on the project, but speaking generally, I'd say that code quality is more important. They say their code doesn't smell, but that's at the start. It's better to enhance performance in bottlenecks. For example, adding an index to a table can be more effective than rewriting the entire engine of the analyzer core.
Once again, we need to think carefully about the current task when deciding on the necessary means. We agree on this viewpoint with Konstantin Volokhovsky, the Java Team Lead:
The main thing is to have a good head on your shoulders and treat recommendations only as recommendations—not rules to follow them blindly.
The same goes for the discussion between using if or switch. From the perspective of writing high-quality and clean code, there isn't much difference. It's possible to grant code a long and quiet life, regardless of the design approach we choose. That's what Uncle Bob said, too. If our code grows more in the number of operations than types, why overcomplicating things?
As I said earlier, clean code is awesome, and performance is important. However, we need to keep in mind what goals we're trying to accomplish. Just don't be zeolitic about these principles.
I asked our developers how often they experienced problems due to strictly following clean code rules and SOLID principles. Only Taras Shevchenko, one of our developers, told me that due to issues in inheritance hierarchies, he once had a delay in the interface update.
I think the performance degradation due to clean code is overstated. When we design our program, we just need to think about what kind of program we want to get.
Let's see if the PVS-Studio developers think code cleanliness is more important than its performance:
Most devs tend to think the same as I do. There's no need for us to prioritize code cleanliness or performance over anything else. We need to keep a balance between well-written code and adequate performance.
Let's take a moment to recap the points covered in the article.
It's useful to talk about whether clean code or performance is more important because it helps us figure out where these concepts diverge. However, I don't think that separating the two concepts one from the other is a great idea, though.
A well-written code should be clear enough that you don't feel like you need to buy a new pair of eyes after reading it. However, don't forget about the program performance, though. Such a compromise will help in getting a good result.
Everything—from writing code to testing and maintaining it—shouldn't be a knee-jerk shot to the workflow. "Moderation in all things"—that's the point I wanted to make with these two articles. I hope we've been able to make a strong case for this position.
"To be always firm must be to be often obstinate. When properly to relax is the trial of judgment..."
Jane Austen