I’ve seen really bad code in my day. I have seen it in legacy code bases, code reviews on greenfield projects, and worse: I’ve even seen it in my own code. In fact, I can open up just about any open source project and immediately detect the existence of potentially bad code.
I am not bragging… it is not as though I am incredibly gifted or that I have superhuman powers. I’ve experienced enough issues from bad code to intuitively pick up on much of it, and the rest of my knowledge comes from reading books on the subject of writing good code. Although I used to detect problem areas on experience alone, I’ve found that having formal names for them aids in identifying and helps in making the case to another developer that something needs to change.
Problem areas are known as ‘code smells’, and it was coined by Kent Beck in explaining to Martin Fowler how he knows when to refactor code. Intuition is still necessary, as the rules for detecting smelly code are presented as guidance. My suggestion is to understand what code smells are and why they are bad, then you can better judge whether source code should be refactored. Sometimes, rules must be broken, but that decision should be made with a clear understanding of the implications. Nearly every time I notice smelly code, a better approach was available.
If you are worried you may have missed something (or even if you are not), download JustCode to see if its state-of-the-art analysis engine picks up anything you should be aware of.
Defined code smells are specific about issues, and learning them can help you quickly identify trouble areas. However, not every piece of bad code has been identified and added to a code smell catalog. I am constantly surprised by the number of ways in which developers can write a bad piece of code. Since the ingenuity of developers outpaces one’s ability to catalog, it is useful to know how to spot bad code without resorting to an index.
Fragile code breaks due to external changes or untested uses of the system.
The longer the fragile code is allowed to remain, the more time it will cost as a project progresses.
Make sure the subsystem has one concern and the classes involved have a single responsibility. If not, take the time to redesign the subsystem so this is the case. Identify tightly-coupled portions of code and use interfaces to break the coupling. Add dependency injection to make it more flexible and easier to test
If this was an important subsystem that slowly became irrelevant, identify what is currently being used and remove the unused code. There will likely be entanglement with the unused portions of the system, and it may lead to an entire rewrite.
Bandaged systems should be given a total rewrite; applying more bandages won’t fix anything.
scream and shout / Mindaugas Danys / CC BY
Software is developed to serve a purpose, and to continue to serve that purpose they must be maintained. Code that poorly communicates what is doing and its intent makes software difficult to maintain.
Optimizing will often make code more complex. This is okay as long as it is documented so other developers understand why the code is less understandable.
Widespread problems must be addressed by making changes at the team or organization level. This includes creating coding standards, implementing testing requirements, and so on. Code bases will require cleaning afterwards, and the decision should be made whether to do it all at once or over time.
Smaller issues should be fixed as they’re encountered: clarify names, simplify complex code (or encapsulate it), apply standard programming idioms, etc.
the rhythm of repetition / eren {sea+prairie} / CC BY
Every piece of knowledge should have one representation. Duplication makes a system more difficult to change as that discrete piece of knowledge has multiple representations throughout the system.
Some code duplication is easy to identify and correct with common refactorings. Other forms of duplication may have identical logic with a different implementation. Having a solid unit testing framework with a ubiquitous language to describe software specifications will make these easier to identify.
Rigid code is resistance to change. Change it, and you have to change something else. Change something else, and you have to change it.
This code must be made more flexible. Decompose into smaller classes and interfaces. You can use the rigid code’s collaborators to identify the appropriate interfaces to use.
Using the generalized smells can help you identify potential problem areas. It is still good to learn more specific smells from books such as Refactoring, as they provide guidance on specific refactorings to perform.
You can also use tools such as Telerik JustCode to quickly detect issues. JustCode identifies problem areas automatically and marks them. If a known fix is available, the quick fix menu will contain the fix so you can quickly refactor your code.
For code cleaning issues that can’t be detected automatically, such as cohesion problems, JustCode provides 30+ intelligent refactorings to automate the work typically done by hand.
Chris is a technical evangelist for JustCode and a C# MVP. He's always sure to bring a can of deodorizer to a legacy code base.
Chris Eargle is a Microsoft C# MVP with over a decade of experience designing and developing enterprise applications, and he runs the local .NET User Group: the Columbia Enterprise Developers Guild. He is a frequent guest of conferences and community events promoting best practices and new technologies. Chris is a native Carolinian; his family settled the Dutch Form region of South Carolina in 1752. He currently resides in Columbia with his wife, Binyue, his dog, Laika, and his three cats: Meeko, Tigger, and Sookie. Amazingly, they all get along... except for Meeko, who is by no means meek.