Writing Solid Code
By Paul Kimmel. email@example.com.
Copyright © 2016. All Rights Reserved.
Prepared for BJones@quinstreet.com
When you get a little older—my wife says I am not old yet—you get to be a cynic. This is a why many older men don't talk (or write) all that much: we know no one is paying attention. Another problem when you qualify for AARP is that you believe what you know is truth and everything else is crap.
In that vein, I could have titled this article "Pair Programming is for Losers," "Why Your Code Sucks," or "Managers are Idiots," but I am pretty sure Herr Jones wouldn't print that. What I can tell you is that all I can write is what I believe, not what you want to hear or what is popular. A lot of what people want to hear or believe is wrong.
I wrote my first lines of code in 1978. One could argue I first got paid to do it in 1988, and I haven't done much else since. And, after reading millions and millions of lines of code, what I can tell you is that most of it is horrible, awful, terrible, and painful to read. Here is why and how you can fix that.
S.O.L.I.D., or There Is No Silver Bullet
SOLID is implied there in the title, so let's dispense with acronyms right off. If you know only the acronym and the word each letter refers to, it is no help. S.O.L.I.D. is Single Responsibility Protocol, Open-Closed, Liskov Substitution Principle, Inversion of Control, and Don't Repeat Yourself. If you ever interview with me, you will need to know a little about Barbara Liskov and Open-Closed because I am not going to ask you about Dependency Injection (IoC).
If one doesn't understand how Liskov may impact the code they are writing—for example, what might be problematic about a square inheriting from a rectangle—then knowing L is for Liskov is useless.
If you want to write "solid" code, you have to explore and investigate underneath the hood. What does it mean to not repeat yourself? What does a single responsibility look like? What is a code inversion? IoC can be accomplished easily with Ninject or Unity, but the what IoC is and the problems it solves tells you why and when to use IoC or dependency injection. More importantly, understanding tells you when you can break the rules. For example, the Law of DeMeter—or Mother Nature—says don't write chains of code like this object1.property2.method3(), but that's exactly what fluent code looks like.
Rule 1: Investigate and explore the meaning and origin of things. Writing sold code is not using mnemonic devices to win who wants to be a gazillionaire.
Rule 2: Rules were meant to be broken.
Code has to have some structure. Just as you wouldn't have a single, monolithic function and call it software you wouldn't have a single class in a single file in a single project. Code has to have a discernible structure. The structure is the solution, not the lines of code. The structure will dictate where code goes and, more importantly, what that code should be. Structure facilitates many hands and testability.
To get a good structure, you need a great designer or architect. Programmers are not designers or architects. Writing lines of code and describing structure are two different jobs that require different kinds of training. Consuming OO code is not the same thing as producing OO code. Any chucklehead can use the code in a framework; most cannot produce a framework.
Rule 3: Your code needs structure, which means your project needs an architect.
Everyone I ever met who considers themselves an architect was generally arrogant and wanted to get paid as much as executives in every organization. A good architect is worth every penny, and calling someone arrogant is just name calling. Get a great architect or get used to failure.
Test Driven Development is not a silver bullet. Writing tests to fail is a waste of time. Why write code to fail when you can write code that does not fail or that is almost right? What matters is that you write unit tests about the same time you write the code it is testing. What is more important is that you write code to coverage, or tests for every line of code and most of the permutations. There are tools that will measure your code coverage for you.
Writing unit tests to coverage is important because you have to test all of the code or you will have more bugs and the lines without tests will likely have nasty bugs. Unit Tests to Code Coverage is not a guarantee, but it eliminates trace bias—testing the way things are supposed to work—and automated tests can be run thousands of times.
Furthermore, you will need those tests because code is not sacrosanct; it is meant to be changed. With code coverage, you can change your code with impunity and to write solid code you will need to write, rewrite, edit, refactor, restructure, and change your code.
Rule 4: Write Unit Tests to Code Coverage.
Rule 5: Be wary of any pointy-headed manager who thinks you will write the code only once; these people are dangerous.
Rule 6: Anyone who thinks manual testing is a good idea is also dangerous.
Every time you Refactor—see Refactoring later—your code or fix a bug, run all your tests. Run your tests daily. Maintain your tests and continuously work for code coverage. I shoot for 90% to 95% as a minimum standard.
Pair programming is a criminal practice. First of all, most programmers aren't writing code continuously throughout an eight or more hour day. To pay for two people and only one of them is writing lines of code is gilding the Lilly; it's a gold-plated toilet.
If you have a really pernicious problem, get a second set of eyes. To pair program as a general practice is a waste of time and money. No good programmer is yielding a keyboard, and you don't want to work with no-good programmers.
Rule 7: If the standard operating procedure is to pair program, hire better programmers.
Code reviews are a waste of time and money. It is an opportunity for people to project their opinions on you, which are both bad and wrong. Don't attend. Don't participate, and for god's sake, never write a coding standards document.
Here is your coding standard: Pick the best programmer you have and tell everyone to write their code so that it is indistinguishable from that person's code. (Unless that person uses abbreviations, underscores, can't Refactor or can't use design patterns, and knows nothing of metrics (see these topics later).)
Rule 8: Avoid formal code reviews; they are a waste of time.
Your senior technologists should be informally keeping an eye on everyone's code as a general practice and making small suggesting and course corrections in private. Public shame and humiliation is not very nice. Mentoring in private achieves better results.
Rule 9: People who need a lot of mentoring can't be on the critical path.
How do you know if the code contributed by an individual has issues? Well, you have standards and tools that measure everyone's work product, which is next.
You don't need group think code reviews because you have standards. Those standards include one man, one keyboard, unit testing to code coverage, and mentoring people but depending on professionals who know their jobs on the critical path.
Your standards also have to include things like great metrics, refactoring, design patterns, 4Cs, boy scout behavior, no code duplicates, avoiding code smells, using words not abbreviations, and avoiding those lying Ted Cruz comments.
Rule 10: Establish standards that define quality and stick to them.
Don't spend time writing comments. Comments lie. Comments aren't compiled. Comments aren't tested. There is no vetting process for comments, and there is nothing that ensures they are have any value or are accurate whatsoever.
Instead of uglying up your code with little lies, write high quality, refactored code with whole words and good names. If you have a well-named method, for example, that has a single responsibility and good metrics, the comment is redundant.
Rule 11: Comments lie and waste time.
Elaborating to exclude specific kinds of comments like authorship comment blocks, which is a feature provided by blame or annotate tools is unnecessary. Comments lie. If you find yourself writing a comment, you need a better name, better metrics, and you will get better metrics by Refactoring and running your unit tests until your metrics are aligned with your standards. Not by writing comments.
Code has to adhere to 4Cs quality. Code has to be cohesive, coherent, convergent, and consistent. Cohesion means the code works well together. You can use patterns like Adapter, but if your code is all mapping and wrappers, you probably have poor cohesion. (Good cohesion feels like Legos.) Coherent means the code makes sense. Code that looks like it was written by drunken monkeys is never going to be solid. Convergent code is code that converges on one instance of code per problem. You can check for code duplicates to eliminate non-convergent (divergent) code. Divergent code is code that exhibits different behaviors for the same metaphor. Consistent code is code that is stylistically similar; it is code that looks like it originated from a single, non-schizophrenic hive mind.
Rule 12: Solid code is cohesive, convergent, coherent, and consistent.
The good news is that incoherent, inconsistent, divergent code with poor cohesion will have lousy metrics and smell like Epstein's locker (bad).
You don't need code reviews because you have metrics. You don't need a coding standards document because you have standards that specify the upper limit on metrics. Metrics are measurements. The metrics for code include lines of code, cyclomatic complexity, maintenance complexity, and computational complexity.
Lines of code means fewer lines of code. My standard is methods with fewer than ten lines of code and preferably just a single line. Although there are instances like constructors that may have several lines of code, long functions generally suggest that the Single Responsibility Principle (SRP) is being violated. Because we are writing code that is based on our values, we won't be violating SRP without a very good reason.
As an aside, people often counter that small functions means more functions, so one is trading lines of code for multiple functions. True. But, functions are the smallest unit of composability that can be reused without duplicating. Ten small functions can be easily understood and re-orchestrated to solve additional problems. Big, old, smelly long functions can only do one thing: breed bugs.
Cyclomatic complexity (CC) is the spaghetti factor or the number of paths through a method. Every path has to be tested, so low cyclomatic numbers are better. A CC of 1 is my preference with an upper limit of 5. A cyclomatic complexity of 5 means you need at least 5 unit tests for this method. Five is not the goal; one if the goal. Five is where mental-flagellation begins.
Maintenance complexity is the overall cost of owning a method—and it could be applied collectively to a class. Again, you want a low maintenance complexity. My value-based upper limit is 100, but over 50 and I get worried.
Computational complexity is measured by Big-O notation. Try to get all of you methods as close to O(1) or O(log n) as you can. O(1) and O(log n) are very simple, bullet proof behaviors. If you must, permit O(n log n ), but run from O(n^2), O(n^k), and O(n!).
Rule 13: Adopt metrics standards for code and measure, measure, measure.
You can't get good metrics without Refactoring and Design Patterns. You can't use Design Patterns and Refactor code (or at least you shouldn't, you kamikaze!) without Unit Tests. That is why all of these elements are necessary; they are balancing functions for each other.
Refactoring is improving design without changing behavior. Refactoring is very specific; you'll need the book. Before you Refactor, you'll need unit tests. What you Refactor is pretty much everything that doesn't meet your metrics standards or that has code smells.
Rule 14: Refactor.
Never let a pointy-headed manager tell you if it isn't broken don't fix it. Code that doesn't meet objective metrics standards is broken, and your manager won't know it and won't understand it anyway.
With Unit Tests, you can Refactor as much as you need to. Be fearless. However, you must follow the steps defined by the Refactoring or use tools and then run your unit tests after your Refactor. If you make a mistake, undo the changes in source control. (Yes, consider yourself especially perspicacious if you inferred source control is required from that last statement.)
Patterns and Anti-Patterns
You will need a book like the GoF book on design patterns and a good book on anti-patterns. How else will you know you are using (incorrectly) Golden Hammer or Stove Pipe (anti-patterns) or correctly the State (pattern) if you don't know what these are. (And, if you don't know patterns and anti-patterns, you aren't the architect.)
Rule 15: Bosses call people architects who aren't to save money. It is cheaper to say you have an architect than it is to hire one—but only in the beginning.
When evaluating code, look for frequent and dense design patterns. Solid code will always look like a design pattern nexus.
There are other elements I think of when I think of clean code. If I ever look at code and wrinkle my nose it's because it smells, and I have a good nose.
You can ask a series of questions when evaluating code. Was this code written by boy scouts? Boy scouts are people that leave things better than they found them. Anti-boy scouts don't change code because they don't want to get blamed for breaking it or they are programmers that patch garbagy, hacky code to "fix" a bug and blow up your metrics. This is littering. (Adding comments to explain hacky, garbagy code is littering too.).
You also can run tools that look for code duplicates. There are a lot of programmers who think that copy-paste programming is okay. It is not. Run from these people or keep them far away from your code.
Sometimes, you can hold up a printed page and look at it in silhouette. If it looks like an x-wing fighter—nested ifs or fors—then you know the cyclomatic complexity metrics are bad.
Rule 16: Run from code smells or people that create them.
You'll also need to run profiling and tuning tools because they will help point out problems that actually exist instead of chasing hunches. Remember that QA is not your testing team. With unit tests to coverage, you'll deliver fewer bugs. Finally, practice, practice, practice. The process is called myelination and you'll need to myelinate the crap out of your brain to write solid code.
For more information about solid code, please see "Testing Solid Code."
I certainly didn't invent all of these ideas. I am not that smart, but I am a collector of knowledge. Some of the best minds in the industry have been writing about this stuff since the 40s. If you want a quick short cut and great read that covers a lot of this material, read Clean Code by Uncle Bob.
What constitutes solid code is subjective. Wrong. Metrics, design patterns, unit tests to coverage, and refactorings are objective. Code that exhibits the qualities defined in this article is solid code. If someone argues with you on this point, flip the bozo bit (or bite the flip bozo), tell them to get the crap out of your office, and when you swagger through the office like a code badass tell 'em Jackson sent you.
Whatever you do, once you are a solid code commando, don't let the pointy-heads promote you into management. You'll hate it.
Paul Kimmel is an architect and code monkey who works on telekinesis and zombie apocalypse survival strategies in his spare time.