Editorial Notes: I originally wrote this post for the Infragistics blog. You can check out the original here, at their site. While you’re there, take a look around at the other posts by their contributing authors.
If you were to take a poll of software development shops and ask whether or not they unit tested, you’d get varied responses. Some would heartily say that they are, and some would sheepishly say that they totally mean to get around to that next year and that they’ve totally been looking into it. In the middle, you’d get a whole lot of responses that amounted to, “it’s complicated.”
In my travels as a consultant, I witness the reason for this firsthand. The adoption rate of automated testing has increased dramatically in the last decade, and that increased adoption means that a lot of shops are taking the plunge. And naturally, this means that a lot of shops with a lot of legacy code and awkward constructs in their codebases are taking the plunge, which leads to interesting, complicated results.
It’s Complicated
“It’s complicated” generally involves variants of “we tried but it wasn’t for us” and “we do it when we can, but the switch hasn’t flipped yet.” And, at the root of all of these variants lies a truth that’s difficult to own up to when talking about your group – “we’re having trouble getting any good at this.”
If this describes you or folks you know, take heart, though. The “Intro to TDD” and “NUnit 101” guides make it look really, really easy. But those sources of learning usually show you how to write unit tests for things like “in-memory calculator,” intending to simplify the domain and code so that you understand the mechanics of a unit test. But, in doing this, they paint a deceptive picture of how easy covering your code with tests should be.
If you’ve been writing code for years with nary a thought to testing at the unit level, it’s likely that familiar, comfortable coding practices of yours are proving to be false friends. In other words, your codebase is probably littered with things that are actively making your life extremely difficult as you try to adopt automated testing. What follows are some of the most common ones that I see.
Editorial Note: I originally wrote this post for the NDepend blog. You can check out the original here, at their site. If you like posts about static analysis, code quality, and the like, check out the rest of the blog.
When I’m called in to do a strategic assessment of a codebase, it’s never the result of everything being awesome. That is, no one calls me up and says, “we’re ahead of schedule, under budget, and knocking it out of the park, so can you come in and tell us what you think of our code?” Rather, I get calls when something isn’t going according to plan and the business people involved want to get some insight into what underlying causes there are in the code and in the team’s approach.
When the business gets involved this way, there is invariably a fiscal operational concern, either overtly or lurking just beneath the surface. I’ll roll this up to the general consideration of “total cost of ownership” for the codebase. The business is thus asking, “why are things proving more expensive than we thought?”
Typically, I come in, size up the situation, quantify it objectively, and then use analogies and examples to make clear what’s happening. After I do this, pretty much without exception, the decision-makers to whom I’m speaking want to know what small things they can do, internally, to course correct. This makes sense when you think about it. If your doctor told you that your health outlook wasn’t great, you’d cross your fingers and say, “but I can fix it by changing my diet and exercise a little, right?” You wouldn’t throw yourself on the table and say, “cut me open and make sure whatever you do is expensive!”
I am thus frequently asked, by both developers and by management, “what are the little things we can do to improve and maintain code quality.” As such, this seems like excellent fodder for a blog post. Here are my tips, based on years of observation of what correlates with healthy codebases and what correlates with distressed ones.
Editorial Note: I originally wrote this post for the SmartBear blog. You can check out the original here, at their site. While you’re there, have a look around and check out Collaborator, the code review tool.
Let’s say that you wanted a definitive explanation, once and for all, as to what constitutes code quality. You might take to google and type “definition of code quality,” which would yield a post from this very blog as well as a sampling of Q&A sites. For the purposes of this post, however, I’d like to examine the entry that occurs here, at programmers’ stack exchange. I think it may be the perfect microcosm for this discussion.
The Q&A Site Consensus
The question is simple enough: “what does it mean to write good code?” The answer receiving the most votes is one in which the respondent draws a parallel to top-notch pool players, who are so good that they set up their next shot as an easy one, even while making the current shot. High-quality code “looks like it was easy and straightforward to do.” This is a classic example of “I can’t define it, but I know it when I see it.” You can recognize code quality because it “looks easy.”
Next up in vote total was a response that cited a popular, somewhat crass cartoon. The gist of it is that code quality is inversely proportional to the number of perturbed utterances per minute on the part of people reading the code. High quality code is code that triggers this response infrequently, whereas low quality code triggers this response vehemently and regularly. Like the answer preceding it, this is not a definition but a recognition heuristic. But with this response, the respondent clarifies that such a definition may actually be impossible, and heuristics are the best approximation.
From there, the answers get more specific and acquire fewer votes each. One respondent offers “how fast can you understand the code” as the defining criteria for high quality code. It is easy to imagine push-back by someone pointing out that “while(true) { }” is trivial to understand, but the resultant application that simply hangs is probably not the product of high quality code. For the most part, the rest of the answers list properties of good code in an attempt to provide the requested definition. “Good code is bug-free, reusable, well-documented, easy to change, etc.” None of these garnered many votes, but each did receive a few.
So what was the verdict? There was no accepted answer and the moderators closed the question as “not constructive.” The reason cited was, “this question will likely solicit debate, arguments, polling, or extended discussion,” thus rendering the question not a fit for Stack Exchange’s paradigm of being a Q&A site with discrete and concrete answers. This is not an indictment of the question, per se, but rather a determination that it’s inherently subjective.
My life has been undergoing some task thrash of late. That, combined with a decent amount of time doing car trips, has thrown me off of my recent reader question streak, in favor of cross posts. But, today I remedy that. A reader has a pretty straightforward question: “how do I write good code?”
Here is the question, verbatim. It references a post that I wrote a while back that has definitely sparked some discussions and follow-up questions.
You write about how to “de-brilliant” your code. I have worked with “brilliant” code in the past and can say that experience is anything but. I’m starting my career as a software dev now and one of the things I’ve been wondering about recently is how to write good code? I think generally that I can recognize well written code from poorly written code, but I want to get even better and I’m not sure how.
First of all, congratulations on the new career! You’re off to a good start. I’d say you’re beginning auspiciously by asking a question that is important, easy to understand, and really, really nuanced to answer. But, let’s try. Here is my general take on how to write good code, in order of priority.
Three weeks, three reader questions. I daresay that I’m on a roll. This week’s question asks about what I’ll refer to as “how to de-brilliant your code.” It was a response to this post, in which I offered the idea of a distinction between maintainable code and common code. The lead-in premise was that of supposedly “brilliant” code, which was code that was written by an ostensible genius and that no one but said genius could understand. (My argument was/is that this is usually just bad code written by a self-important expert beginner).
The question is, as follows, verbatim.
In your opinion, what is the best approach to identify que “brilliant” ones with hard code, to later work on turn brilliant to common?
Would be code review the best? Pair programming (seniors could felt challenged…)?
Now, please forgive me if I get this wrong, but because of the use of “que” where an English speaker might say “which”, I’m going to infer that the question submitter speaks Spanish as a first language. I believe the question is asking after the best way to identify and remediate pockets of ‘brilliant’ code. But, because of the ambiguity of “ones” it could also be asking about identifying humans that tend to write ‘brilliant’. Because of this, I’ll just go ahead and address both.
Find the Brilliant Code
First up is identifying brilliant code, which shouldn’t be terribly hard. You could gather a quorum of your team together and see if there are pockets of code that no one really understands, or else you could remove the anchoring bias of being in a group by having everyone assess the code independently. In either case, a bunch of “uh, I don’t get it” probably indicates ‘brilliant’ code. The group aspect of this also serves (probably) to prevent against an individual not understanding simply by virtue of being too much of a language novice (“what’s that ++ after the i variable,” for instance, indicates the problem is with the beholder rather than the original developer).
But, even better, ask people to take turns explaining what methods do. If people flounder or if they disagree, then they obviously don’t get it, self-reporting notwithstanding. And having team members not understanding pockets of code is an ipso facto problem.
An interesting side note at this point is whether this illegible code is “brilliant” or “utter spaghetti” is going to depend a lot more on knowledge of who wrote it than anything else. “Oh, Alice wrote that — it’s probably just too sophisticated for our dull brains. Oh, wait, you were reading the wrong commit, and it’s actually Bob’s code? Bob’s an idiot — that’s just bad code.”
De-Brilliant The Codebase
Having identified the target code for de-brillianting, flag it somewhere for refactoring: Jira, TFS, that spreadsheet your team uses, whatever. Just make a note and queue it as work — don’t just dive in and start mucking around in production code, unless that’s a team norm and you have heavy test coverage. Absent these things, you’re creating risk without buy in.
Leave these things in the backlog for prioritization on the “eventually” pile, but with one caveat. If you need to be touching that code for some other reason, employ the boy-scout rule and de-brilliant it, as long as you’re already in there. First, though, put some characterization tests around it, so that you have a chance to know if you’re breaking anything. Then, do what you need to and make the code easy to read; when you’re done, the same, “tell me what this does” should be easy to answer for your teammates.
De-brillianting the codebase is something that you’ll have to chip away at over the course of time.
De-Brilliant The Humans
I would include a blurb on how to find the humans, but that should be pretty straightforward — find the brilliant code and look at the commit history. You might even be able to tell simply from behavior. People that talk about using 4 design patterns on a feature or cramming 12 statements into a loop condition are prime candidates.
The trick isn’t in finding these folks, but in convincing them to stop it. And that is both simple to understand and hard to do.
During my undergrad CS major many years ago, I took an intro course in C++. At one point, we had to do a series of pretty mundane, review exercises that would be graded automatically by a program (easy things like “write a for loop”). Not exactly the stuff dreams are made of, so some people got creative. One kid removed literally every piece of white space from his program, and another made some kind of art with indentations. When people are bored, they seek clever things to do, and the result is ‘brilliant’ code.
The key to de-brillianting thus lies in presenting them with the right challenge, often via constraints or restrictions of some kind. They do it to themselves otherwise — “I’ll write this feature without using the if keyword anywhere!”
The Right Motivation
Like I said, a simple solution does not necessarily imply an easy solution. How does one challenge others into writing the kind of straightforward code that is readable and maintainable?
Code review/pairing presents a possible solution. Given the earlier, “can others articulate what this does” metric, the team can challenge programmer-Einstein to channel that towering intellect toward this purpose. That may work for some, but other brilliant programmers might not consider that to be a worthwhile or interesting challenge.
In that case, automated feedback through static analysis might do the trick. FXCop, NDepend, SonarQube, and others can be installed and configured to steer things in the general direction of readability. Writing code that complies with all warning thresholds of such tools actually presents quite a challenge, since so much of programming is about trade-offs. Now, a sufficiently determined clever coder could still invent ways to write hard-to-read code, but that would be a much more difficult task when he’d get slapped by the tool for chaining 20 Linq statements onto a single line or whatever.
Of course, probably the best solution is to work with the sort of people who recognize that demonstrating their cleverness takes a backseat to being a professional. They can do that in their spare time.
If you have a question you’d like to hear my opinion on, please feel free to submit.
I am Erik Dietrich, founder of DaedTech. I’m a former programmer, architect, and IT management consultant, and current founder and CEO of Hit Subscribe.