I had this car once. I loved the thing, but, before the end of its life, my wife and I had developed sort of a running joke about it. Specifically, if you wanted to see the “check engine” light come on, take the thing on a road trip. About 100 miles in, that light would come on.
The fog of memory has probably colored this tale somewhat. I can’t imagine that this happened before literally every driving trip we took. But it sure seems like it did. I can vividly recall the feeling of “something’s wrong” when we’d come too far to reasonably turn back but still had most of the trip in front of us.
Against this backdrop, the wisdom of the software aphorism, “fail fast” hits home. Had the light come on as we sat in the driveway, about to leave, we’d have had options. Take my wife’s car. Go to the dealership on the way out of town to make sure we could safely drive. Something. But, 100 miles into the trip, those options narrowed to “just keep going and hope for the best.”
If you must fail, better to do so early.
Fail Fast with Software Builds
When I first started as a professional software developer, “the build” tended to mean something much different than now. We’d code furiously for months or years, and then someone would blow the “code freeze” whistle. Then, after some testing, the “build guy” would pull the latest stuff out of CVS or Visual Source Safe and work on turning it into something that could be burned on a CD and distributed. The first inkling of possible build problems would come something like 96% of the way through the project. Heck, at that time, we had “merge parties,” wherein merging and integrating one another’s separately developed source code could take days.
This created serious risk for projects. We had the ability to unwittingly introduce defects that would not manifest until perilously close to project release time. Like my potentially ill-fated driving trips, a too-late warning could create unacceptable delays and logistical problems.
So we, as an industry, learned to fail faster. We brought in the concept of continuous integration and a build machine. At the very beginning of a project, set the thing up for regular integration and delivery to a production (like) environment. Know quickly if you introduced something that would gum up the downstream works.
The Refinement of Fail Fast
In the early days of this approach, we answered relatively simple questions. “Can we keep this thing in a state where it will compile and then run where we want it to?” “Can we manage not to clobber each others’ changes?”
But the inexorable march of progress began to give us more. If we could make builds frequent and automatic, why not run some tests while we were at it? And so we started to add the unit test suite to the mix. Now we could fail the build on non-compile, but also on focused, targeted runtime concerns. In other words, we could execute the software in precise ways and reject it for the wrong behaviors.
From there, we branched out into other sorts of tests (end to end, smoke, acceptance, integration). We also started to ask the question, “have we tested this thing sufficiently?” We measured this by insufficient test coverage, and we could fail the build as a result of it.
But static analysis provided more. Not only could we fail for inability to compile and for incorrect runtime behavior, but we could also fail the code for egregious sins in the code itself. We added the ability to inspect the code and kick back a “nope!” for non-compliance.
We can generally refer to this last instanced of fail fast as a “quality gate.” We point static analyzers at the application’s source code. And then, for certain undesirable qualities of that code, we fail a build by saying, “nope, not until you fix that.”
While newer than the concept of automated builds themselves, we’ve had quality gates for a while. Initially, one might have accomplished it by creating a custom integration between a static analyzer and a build tool. But, over the course of time, both build tools and static analyzers have developed hooks and capabilities to make this more turnkey.
With NDepend, we have no exception. I’ve used it for years to fail builds that exhibited certain undesirable properties. But now, you have quality gates as a first class feature of NDepend.
NDepend’s Take on the Quality Gate
Why do I find this particular development so compelling? Simply put, I like it because it marries the amazingly powerful CQLinq with the ease of a first class build hook. In other words, you can define a totally custom quality gate and use it to fail the build more easily than ever.
Let’s go into my Chess TDD codebase for a quick example. I opened up the NDepend dashboard and wrote myself a quick quality gate called “Spurious Quality Gate.”
// <QualityGate Name="Spurious Quality Gate" Unit="types"/>
failif value > 0 types
JustMyCode.Types.Count(t => t.Name.StartsWith("A"))
I warned you it was spurious. Just in case you can’t readily decipher CQLinq, this quality gate translates to “fail if the codebase has any types whose names start with the letter A.”
The particulars of the semantics for quality gates could occupy an entire post. (I may later make that post). But for now, think not of the details but of the possibilities. And I don’t mean the possibility of turning yourself into a demented, power-mad architect that outlaws classes starting with A. I mean the possibility of creating insanely detailed checks and balances with your code.
I used a spurious one to make it memorable. But you could used a nuanced one to guard against anti-patterns and steer direction.
The Power of the NDepend Quality Gate
Over the years, NDepend has evolved considerably. You can work it easily into your build, and now you can, just as easily, define quality gates. You’ve had the impressive power of CQLinq at your disposal for some time now.
NDepend has come a long way, and so too has the fail fast nature of the software build. You should leverage both.
When I started programming, we would wait months or years to learn whether our code would even compile once integrated, let alone to run in production. Today, you can fail the build on the first introduction of a dependency cycle or a nasty, complex method. To circle back to the introductory metaphor, you now have the ability to ask your car whether it’ll be up for a road trip next week and to get an accurate answer. I suggest you avail yourself of this ability.