The other day, I was watching this video of Robert Martin where he mentions the three symptoms to spot a bad code. And I couldn’t have agreed more.
1. Rigid Code
Your code is rigid if you can’t change a piece of code without changing the other modules that are irrelevant and have no relationship to the code you try to change.
Rigid code is the code that has dependencies that snake out in so many directions that you cannot make an isolated change without changing everything else around it. — Robert Martin
For instance, you try to change some low-level detail in the data layer, and all of a sudden you get a compile error in the class that does the formatting for your View.
If you often find yourself touching almost all the modules of your project, whenever you make changes in a single class, it might mean a symptom of rigid code.
2. Fragile Code
Fragile code is a lot worse than Rigid code. At least we get a compile error in a rigid code.
In fragile code, if you change a small piece of code in a module, you break an entirely different feature in an entirely different module. And there is no easy way to find these errors during development.
Fragile code breaks in bizarre and strange ways that you cannot predict. — Robert Martin
If you often get customer tickets saying a feature is broken, whenever you push a fix for an entirely different use case, it might mean your code is fragile.
3. Non-reusable code
Non-reusable code is, as the name suggests, the code that cannot be reused.
For instance, you might want to implement a feature in your project that is the same as the one your colleague has done for a different project. But you cannot easily reuse the code from that project because that code depends on some other irrelevant feature of their project which in turn depends on some framework or database system which you don’t want in your project. And you better off writing your own implementation for that feature.
This is the famous Gorilla-Banana problem:
You want a banana but what you get is a gorilla holding a banana and the entire jungle with it. — Joe Armstrong
How can we avoid these 3 flaws?
When you write code, ask yourselves these questions:
Is this code too rigid? Is it possible to change the internals of this module in the future without touching the code in other modules and other layers?
Is this code too fragile? Will it be hard to find all the places to refactor for any changes in the future?
Should this feature be reusable? If so, does this code depends on any unwanted modules or frameworks that can be avoided?
If we look closely, the common thread of all the above three problems is coupling. The modules depend upon each other in undesirable ways and that results in spaghetti code.
The code should be decoupled across the modules and layers. High-level policies and the abstractions should not depend on low-level details. Invert the dependency of the modules at the necessary places. And write classes that do only one thing and have only one reason to change.
Good code should explain what it's doing. It should be boring to read. Everything is perfectly obvious. That is good code. — Robert Martin
Also published in my Medium publication.
Top comments (13)
From experience the point number 3 is a huge source of problem. it tend to increase rigidity and fragility.
The thing is making code truly reusable is really difficult and costly. It require lot of experience, budget and commitment to make an API rather than just a program that solve a single problem. I speak of API on purpose. An API is fully backward compatible, fully documented (even if you don't like it), comes with tutorials and so on, deal with all corners cases (and has a defined and documented behavior for all). It also typically optimized for performance.
If you achieve that, your code is really usable, in particular you can reuse that code without severely affecting rigidity and fragility of your software.
On the contrary, reusing code without promoting such code as an API is sure to increase rigidity and fragility. Because the code is not truely generic. Because the code make some shortcut maybe, because the perf is not so great but was acceptable in the other use case. For lot of reason that code doesn't achieve the quality an API require. So it isn't really reusable.
I remember reading that a program something that managed to work if you provide it the right inputs/env typically cost 1 unit. This is like a prototype or a script to solve an issue. Then there is software. A program that is validated, handle lot of error case and is robust enough to do its job in variety of env. The cost of a program is 3 unit. Then there APIs, truely reusable code that is rock solid and you can struct and build software and programs on top of it. Like Java API, spring, hibernate or the win32 runtime. Because of all of this, the cost of an API is 10 unit.
I see that a great share of problems of rigidity and fragility come from tentative of reusability without really investing enough in it. when you aim for reusability, you should agree to pay the associated cost. Most of the time you or your management will not want to pay that cost. So it make much more sence to reuse existing and validated API, typically open source than try to make your own...
Thx for this perspective, I really like the analogies.
These are great points - but I have one question: How do you know you're writing bad code when you're writing it?
Despite having been writing code for like 15 years, I still sometimes find it hard to spot it when writing it. Yeah, I can go and try and change it later and I'll realize "oh this is kind of crap", but how do I prevent myself from writing it in that way in the first place?
My best solution so far has been TDD, but I'd be curious to hear if anyone has any other solutions :)
Robert Martin's Clean Code is a book that instructs a person how to see bad code, and how to fix bad code.
The book points out that these are skills that can be learned. And that they are difficult skills to acquire, so it is set up as examples and exercises to help learn the skills. For a wide variety of bad code (anti)patterns.
I have read somewhere before that the code for a feature or a module has to be written 3 times to get it right. :P
If you have to explain every detail of your code, and there are lots of questions, that code probably isn't very good and could be improved. Improve areas with the most questions and explaination. Even code that implements a complex mathematical algorithm should be easy to understand as long as others know the algorithm.
Yes, I agree. I even restrain myself from writing comments over the code to explain the code and try to refactor the code so that it explains itself.
Good article, but as a newbie React.js developer it seems that I can't set myself to follow these rules especially when it comes with state management in Redux, where there is a global store and many files are importing "actions" which they are responsible for updating it when they are called, but it doesn't mean that changing something is gonna be hard or the code is not going to be totally reusable, the architecture itself imposes this way of doing things, I hope that a professional React/Redux programmer is reading my comment and he/she has something to say or recommend some of the best practices...
Agreed, obviously. :) But you should add a social component and team play to it. From that point of view, "good code" is code most if not all devs in the team can easily work with, code that allows for finding and fixing bugs easily, code that helps implementing new features and delivering value fast. If in doubt I would prefer that approach and let the team come up with "clean code" definitions of its own. Code reuse is a good example: We've seen a load of situations where we started with "reusable" code moved into shared libs rather than (cough) copy-paste'ing a bunch of files around - and found ourselves ending up in considerable library versioning effort. Our solution to that, so far, has been making the team obviously do code reviews for each git pull request and make sure that, asides from being able to build and run code, at least two or three guys on the team accept that code as "clean enough". So far this works quite well. ;)
Yes, code reviewing and having a set of standard patterns for normal use cases is very important to have a clean code base, which is readable and maintainable by anyone in the team.
WordPress.
Code complete 2 is a good book
I have heard about that book at a lot of places. It's on my reading list. ;)