To differentiate the books, Clean Code is all about how to make sure that your output (the code) is well written and intentional (not simply slapped together). The Clean Coder is all about how to make sure that you (the coder) are professional and intentional.
Clean Code is about identifying "code smells" -- those things that aren't quite right about code. Well-seasoned developers are (generally) adapted to identifying the smells. We've come across code smells in previous articles (including here and here) and also in Robert C. Martin's book Agile Principles, Patterns, and Practices in C#. This book (Clean Code) has an entire chapter devoted to common code smells (and also an appendix that cross-references the code smells throughout the other chapters).
Just by looking through the table of contents, we can glean a lot of useful tips. (I'm not quite sure what to call these: "rules" sounds too strong, and "tips" does not sound strong enough.) Here are just a few:
Chapter 2: Meaningful Names
- Use Intention-Revealing Names
- Use Pronounceable Names
- Don't Be Cute
- Pick One Word per Concept
- Do One Thing
- Have No Side Effects
- Don't Repeat Yourself
- Comments Do Not Make Up for Bad Code
- Explain Yourself in Code
There are some things that you may not agree with -- and that's okay. What's important is that we are thinking about why we are making a decision one way or another. A lot of times, we may find that we don't have a good reason for doing things the way we do (other than "that's the way I've always done it" or "that's the way I was taught").
At the same time, there will probably be many things that you agree with but don't do. As I was reading, I constantly found myself thinking about my current project and how I was or was not applying these tips.
An Iterative Process
One of my biggest take-aways from Clean Code is that clean code is not something that comes out in the first iteration. We need to revisit our code and refactor as necessary. Often it takes several iterations to get a piece of code right.
Several of the chapters show the process of refactoring actual code. This shows the steps and thought process behind the decisions as they are made. In addition, it shows that sometimes we may end up reversing a previous refactoring as we go further down the path.
This iterative process also means that we are not optimizing or abstracting too early in the process. When we first write a block of code, we may not know that we need to use it in other parts of the application. But if we find ourselves doing a copy/paste, we should pull that code out into a separate method or class rather than duplicating it.
The iterative process also applies to the complexity of a method. As an example, in my current project, we have initialization code for each module. These often start out small, with just four or five lines of code. But as more functionality gets fleshed out, the initialization routine may grow in complexity -- often with if/else or switch statements. The method doesn't end up very long (just about 30 lines or so); however, by thinking is smaller chunks, we can split that 30 lines into 3 or 4 smaller, more focused methods. Then our initialization routine just has 3 or 4 lines of code (that call those separate methods), and if we name the methods meaningfully, then it is very clear what the initialization method is trying to accomplish. If we need details, then we can always drill down into the specific methods.
Unit Testing / TDD
Unit Testing and TDD (Test Driven Development) are extremely important when going through this iterative process. If we have good unit tests in place, then we can be confident that we did not break the functionality when we move or refactor code. If we do not have unit tests in place, then we cannot be sure.
I have experienced this in my own code. Where I have good unit test coverage, I can make updates confidently. And if something breaks (either in the code or the unit test), then I know that something is wrong. Sometimes I need to update my code, and sometimes I need to update the unit test to reflect different functionality. But if we are simply refactoring (with no functionality updates), then our unit tests should continue to pass without modification.
Why Do We Care?
In most situations, we will not maintain the code that we write forever. Someone else will need to take it over at some point. At the same time, we have had to take over someone else's code. Think about your own experience. Sometimes it is good -- the code is clear and you can jump right in to making changes. Sometimes it is bad -- and you have to untangle the intention before you can make any modifications to the code.
I try to live with this approach: Always assume that the person who has to maintain your code is a homicidal maniac who knows where you live. This is especially important in my current role as a contract programmer. I know that after I leave this project, someone else will have to maintain (and enhance) the code that I write. My goal is to make sure that the people who come behind me can do that with as little difficulty as possible. I don't want to be "that contractor who wrote the code no one can understand." I want to be the guy who built a good foundation for the future of the application.
Clean Code has made me think a lot about the approach I take to my code. The good news is that I found that I recognize most of the code smells enumerated in the book. But there is always room for improvement, and the areas that I did not previously recognize now jump out at me.
Being a developer is constant learning. Many times we are learning new technologies. But we also have to stop and re-evaluate coding style and best practices. There are many smart people who have gone before us. We can benefit from their experience and build better, more maintainable projects.