Search This Blog

Tech Book Face Off: Clean Code Vs. Agile Principles, Patterns, and Practices in C#

It's been two months since I've done a Tech Book Face Off, but not because I haven't been studying and learning. Things have been crazy busy lately, so I had to slow down on the reading a bit. I've still managed to sneak these two great books into my limited free time, and now it's time to give them a rundown.

I've read a lot of things that refer to Agile development or talk about bits and pieces of it, but I never really read anything that presented a complete picture. I've also repeatedly been told that many of the things I talk about on this blog are covered thoroughly by Robert C. Martin, better known as Uncle Bob. It was about time that I studied Agile in more depth, and it seemed like a good time to check out some of Uncle Bob's stuff since he's an outspoken advocate of Agile. I decided on two of his books: Clean Code: A Handbook of Agile Software Craftsmanship and Agile Principles, Patterns, and Practices in C#. Let's see what I've been missing.

Clean Code front coverVS.Agile Principles, Patterns, and Practices in C# front cover

Clean Code

First things first, I love the covers. Images of deep space objects are always inspiring to me. I said quite a while ago that I wanted to read Clean Code, and only now have I finally gotten around to it. I often think when reading a good book that I should have read it sooner, but when would I have fit it in, really? I read a lot.

I do feel that Uncle Bob was speaking directly to me through this book. It was excellent. Nearly all of the programming related stuff I write about here is already in Clean Code—well organized, and clearly presented. I'm amazed at the amount of overlap in our thinking; although I don't agree with him on everything.

The book is focused on what clean code looks like and how to write code well. It starts out simple with recommendations on naming conventions, functions, comments, and formatting. Then it builds on that foundation with objects and data structures, error handling, component boundaries, unit tests, classes, and systems. It then moves into high-level issues with emergent design, concurrency, and successive refinement, and finishes up with a nice example of how to put all of this advice to work on refactoring a SerialDate class. All of the code smells and heuristics are then listed at the end for convenience.

I thoroughly enjoyed reading this book, and I'd like to share some of my favorite quotes to give a sense of the tone and style of the writing. I'll do my best to limit myself. Here's one from the first chapter, and it's actually a quote from Michael Feathers, author of Working Effectively with Legacy Code, which I have not yet read:
I could list all of the qualities that I notice in clean code, but there is one overarching quality that leads to all of them. Clean code always looks like it was written by someone who cares. There is nothing obvious that you can do to make it better. All of those things were thought about by the code’s author, and if you try to imagine improvements, you’re led back to where you are, sitting in appreciation of the code someone left for you… 

Even though this was written by a different author, it summarizes quite succinctly what clean code is. Of course, it says nothing about how to make your code clean, but it certainly gives you a good test for deciding when you've reached that goal. It's humbling to think about how much more work I have yet to do before I can claim to write code to that standard. I am constantly looking at my code and thinking that it could be expressed more clearly, concisely, or flexibly. There is a balance between those characteristics that I haven't quite mastered, yet. That is the focus of Clean Code, and much of the advice takes the form of how to write code well:

Writing software is like any other kind of writing. When you write a paper or an article, you get your thoughts down first, then you massage it until it reads well. The first draft might be clumsy and disorganized, so you wordsmith it and restructure it and refine it until it reads the way you want it to read.
I like the idea of programming being like writing. I used to be afraid to change code to make it read better, probably like many novice programmers. Getting a program to do what it was supposed to was hard enough. Why risk breaking it? But as I became more comfortable and confident with code, due in no small part to better coding practices like version control and unit testing, I began to realize the value of well-written code. Now I strive for it in all of my programming, and I'm not afraid to temporarily break code on the way to making it better. Progress is best made by focusing on writing better code instead of explaining bad code through comments:
The proper use of comments is to compensate for our failure to express ourself in code. Note that I used the word failure. I meant it. Comments are always failures. We must have them because we cannot always figure out how to express ourselves without them, but their use is not a cause for celebration.
Sometimes comments are a necessity, but they shouldn't be used as a crutch. Many times comments are a code smell. Maybe the code needs better variable names, maybe it needs a good function wrapped around it, but somehow the code could be expressed more clearly than it is because you're having to explain yourself.

In the chapter on writing classes, Uncle Bob goes through an example where he refactors a program that generates prime numbers, and explains why clean code is not necessarily smaller code:
It went from a little over one page to nearly three pages in length. There are several reasons for this growth. First, the refactored program uses longer, more descriptive variable names. Second, the refactored program uses function and class declarations as a way to add commentary to the code. Third, we used whitespace and formatting techniques to keep the program readable. 
Dense code can be very unclear, and improving it generally means making it longer. In this particular example, I'm not in total agreement that the end result is all that readable, either. Some of the method names are too long and don't accurately describe the essence of the algorithm, some classes would have been better off as collections of functions if the language used hadn't been Java, and I really prefer snake_case to CamelCase. Snake case is closer in appearance to normal writing, with underscores in place of spaces. While camel case smashes all of the words together and uses unnatural capitalization to attempt to mitigate the damage. The result is far less readable.

Even though the example in the book made the code longer, I've seen plenty of overly verbose code that's also confusing and is greatly improved by making it shorter. The point is that code is more understandable if the pacing is right, just like writing, and the author should take pacing into consideration when writing code. Regardless of my disagreement with the result of this example, I fully agree with the points Uncle Bob was making, and it helped clarify my own thoughts on what clean code should be.
We want to structure our systems so that we muck with as little as possible when we update them with new or changed features. In an ideal system, we incorporate new features by extending the system, not by making modifications to existing code.
This gave me a chuckle, because I know how it can be taken to the extreme—never remove anything from the system, only add more. I know Uncle Bob is not advocating this, but I thought it was funny. I know design teams that are afraid to remove or change anything in a working system. We should always remember: 
Although software has its own physics, it is economically feasible to make radical change, if the structure of the software separates its concerns effectively.
Here software is being compared to buildings, and software has the distinction of being able to be changed in place. Making changes to a building by ripping parts of it out and building in different structures is normally too expensive and not ideal. As long as the building is functional, the next iteration is done by constructing another building with any new ideas (save home renovation). Software can be iterated in place to try out ideas, and it's often more expensive to rewrite everything from scratch than to change the existing software to incorporate those ideas.

The whole book is filled with great advice on writing clean code. When I reviewed The Pragmatic Programmer and Code Complete, I wondered if the content of Code Complete could be sufficiently covered by the combination of The Pragmatic Programmer, Clean Code, and Refactoring in less pages. Without having read Refactoring, I now think that the other two books pretty much cover what Code Complete does in a more enjoyable, more engaging way. Clean Code should definitely be in every programmer's library.

Agile Principles, Patterns, and Practices in C#

Preface: This book was co-authored by Robert Martin's son Micah, but I'll generally refer to things as written by Uncle Bob since I'm not sure who wrote what.

This book (APPP for short) is made up of 4 sections that cover the Agile development process, Agile design with the SOLID principles, and two case studies of a non-trivial payroll software system and a package analysis of the payroll software. The book also covers what you need to know about UML diagrams and most, if not all, of the design patterns from Design Patterns. All in all, it packs a ton of useful information into a very readable package. It's the only book you really need to read on UML, and maybe all you need on design patterns as well. However, Design Patterns goes into more depth and rigor, so you may want to dig into it anyway.

Like Clean Code, APPP has a lot of great advice in it, and I really enjoyed the relaxed, personable writing style. I didn't agree with everything, though. For example, when describing pair programming Uncle Bob says:

The team works together in an open room. … Two chairs are in front of each workstation. The walls are covered with status charts, task breakdowns, Unified Modeling Language (UML) diagrams, and so on. The sound in this room is a buzz of conversation. Each pair is within earshot of every other pair. Each has the opportunity to hear when another is in trouble. Each knows the state of the other. The programmers are in a position to communicate intensely. One might think that this would be a distracting environment. … In fact, this doesn’t turn out to be the case. Moreover, instead of interfering with productivity, a University of Michigan study suggested, working in a “war room” environment may increase productivity by a factor of 2.

Other studies show programmers are more productive in offices with walls and doors. In fact Peopleware, another well-respected book on software development, strongly advocates for private offices. So who's right? Currently, I'm working in an entirely open office, and I'm really enjoying it. We have some of the good elements of more communication and problem solving help, but other than that, it doesn't sound too much like Uncle Bob's depiction. If there was a "buzz of conversation" more than a few times a day, I know I wouldn't be able to get much done. It's not just distracting, but downright disruptive when you're in flow. In a noisy environment like this it might be required to pair program in order to brute force forward progress. How is that going to work when you need to think deeply about a problem without interruption? I find myself deep in thought most of the time, and I appreciate that the office is generally very quiet.

While I generally disagreed with their ideas on the optimal office environment, I found myself agreeing much more with their take on UML diagrams:

So, yes, diagrams can be inappropriate at times. When are they inappropriate? When you create them without code to validate them and then intend to follow them.
Indeed, code can reveal constraints on a problem that diagrams easily gloss over. The authors get even more serious later on before describing the different UML diagrams in detail:
Before exploring the details of UML, we should talk about when and why we use it. Much harm has been done to software projects through the misuse and overuse of UML.
And then they bring down the hammer:
It is not at all clear that drawing UML diagrams is much cheaper than writing code. Indeed, many project teams have spent more on their diagrams than they have on the code itself. It is also not clear that throwing away a diagram is much cheaper than throwing away code. Therefore, it is not at all clear that creating a comprehensive UML design before writing code is a cost-effective option.
Yes! Basically, use UML when it is useful for clarifying ideas, exploring a system, or experimenting with options in a rough and basic way. If you're moving towards UML diagrams as documentation or worse, executable UML (shudder), you've taking a wrong turn into architecture astronaut land. Uncle Bob also tries to give you a wake-up call when resisting design changes:
We might complain that the program was well designed for the original spec and that the subsequent changes to the spec caused the design to degrade. However, this ignores one of the most prominent facts in software development: Requirements always change!
Accept it already. The design is going to change, even the one you're working on now, especially the one you're working on now. Don't blame things you can't control. Find something you can control, blame that, and then fix it! It's your job. You were hired to be a problem solver, so solve some problems.

They also take some digs at software documents:
The value of a software document is inversely proportional to its size.
And too much up-front design:
If we tried to design the component-dependency structure before we had designed any classes, we would likely fail rather badly. We would not know much about common closure, we would be unaware of any reusable elements, and we would almost certainly create components that produced dependency cycles. Thus, the component-dependency structure grows and evolves with the logical design of the system.
This gets at the fact that it's very hard to know what a software system will look like until it's finished. Accept what you don't know, and don't try to plan beyond your horizon.

I'll wrap up with one more quotation that I don't entirely agree with:

That is the problem with conventions: they have to be continually resold to each developer. If the developer has not learned the convention or does not agree with it, the convention will be violated. And one violation can compromise the whole structure.

This idea is interesting when compared to the push for convention over configuration, especially in Ruby on Rails, a fairly successful framework that epitomizes convention. I can see where Uncle Bob is coming from, but there is so much grey area and context dependence here that general statements can't hold much water. I would say that conventions definitely have their uses in well-defined situations.

Overall, APPP was an excellent book. The opinions were sharp, the analysis was clear, and the explanations were thorough. The book filled a lot of holes in my understanding of Agile development, and I enjoyed all of the pragmatic coverage of Object-Oriented Design principles, UML diagrams, and design patterns. Working through this book will greatly help you in becoming a better programmer.

Two Sides of a Coin

These two books are nicely complimentary. Clean Code deals with the format of the code at a fundamental level, and how to make it readable and understandable. APPP tackles how to design a software system effectively and covers many of the Agile tools available to improve the process. They are both quite valuable for improving your skills as a programmer. Working through the extensive code examples and doing the work of understanding all of the practices presented will help you reach the next level in your programming endeavors.

No comments:

Post a Comment