I have accepted an invitation to speak at GSP Developers in March. My topic will be Test Driven Design. I will focus on how practicing TDD changes the overall design of your software.
I think my strategy should be:
Give a brief overview of TDD.
Demonstrate writing a simple function TDD style.
Discuss what is meant by “testable” code, meaning how testable code relates to the SOLID design principles.
Give a brief overview of the available TDD tools for .NET.
Any advice for a first-time speaker?
No, I’m not talking about Coors Light. I”m talking about that phrase you hear bandied about in software development circles, “There is no silver bullet.”
Wikipedia describes the meaning of the phrase thusly:
The term has been adopted into a general metaphor, where "silver bullet" refers to any straightforward solution perceived to have extreme effectiveness. The phrase typically appears with an expectation that some new technology or practice will easily cure a major prevailing problem.
Historically it comes from a 1986 paper by Fred Brooks. Again from Wikipedia:
Brooks argues that "there is no single development, in either technology or management technique, which by itself promises even one order of magnitude [tenfold] improvement within a decade in productivity, in reliability, in simplicity." He also states that "we cannot expect ever to see two-fold gains every two years" in software development, like there is in hardware development.
Accidental complexity relates to problems that we create on our own and can be fixed — for example, the details of writing and optimizing assembly code or the delays caused by batch processing. Essential complexity is caused by the problem to be solved, and nothing can remove it — if users want a program to do 30 different things, then those 30 things are essential and the program must do those 30 different things.
Brooks distinction between accidental and essential complexity is not what most people have in mind when they invoke “no silver bullet.” Typically they are directing the statement toward some developer or manager who is in the throes of excitement over a new technology or practice. The statement seems to mean, “there’s no reason to get excited. Your new tool will have its own problems. It’s not really a step forward. You’re just naive.” It is this usage that I have a problem with.
The reason it bothers me is because it’s often me that is excited by some new technology or process. And every time it happens, I get “there is no silver bullet” tossed in my direction from someone. Fred Brooks is right, we’re not going to see the kinds of gains in software productivity that we see in hardware productivity. However, to all those that use “no silver bullet” to kill excitement, I must disagree.
There is a silver bullet to solving software problems. Are you ready? Do you want to know what it is? What company distributes it? How much it costs?
It’s your brain. You distribute it, and the cost is the effort of constantly learning new and better ways to accomplish your tasks. It doesn’t matter what language you use, what platform you write for, how you set up your environment. All of that is just the car—it still needs a driver. And no matter how great the car, if the driver is only holding the gas at 25mph, you’re only going to go 25mph.
You didn’t learn a single percent of what you need to know in college. You have to think critically about everything you’ve ever been taught. You have to look at the software you find and ask archeological questions such as “Why did the original developer structure the code this way? What problems was s/he trying to solve? Was this a good solution? Can I think of alternatives? Is this the best solution?” You have to ask these questions about software written by everyone, including yourself, even if you just finished typing it in.
If you adopt as a principle the idea that you should see every line of code you write as an opportunity for improvement, you will start making small improvements to your code. At first, you’ll start renaming variables to more clearly communicate intent. Then you’ll start extracting functions into smaller and smaller functions until most of them are only a few lines long. After awhile, these improvements will simply become second nature and your overall code-base will be clearer to you. In the clearer code-base, you’ll start to see other things that you can improve—things that were hidden from you before, much like a hole in the wall can be hidden by a pile of laundry. Historically, most software becomes a vicious cycle of layering hack on top of hack until the software atrophies and dies and has to be rewritten. Rinse and repeat. You can reverse the direction of that cycle—make it a virtuous cycle—by layering small improvement on top of small improvement, so that it will prosper and flourish.
On the surface, your job is to write software that solves a specific problem within a certain timeframe. But your job is also to make sure that that software will survive in the face of change. The first part is what they can teach in college. The second part is much harder. Learn it.
Read the blogs of other developers. Read books about the profession. Listen to podcasts about technology. Become active-minded about your profession. And next time some developer is excited because s/he’s found some better way of doing things, don’t say “there’s no silver bullet.” Say, “great job—how can I use that?”
Often as a software engineer my team is faced with a choice between two or more options for how to solve a particular problem. What will ultimately end up being the correct choice depends on information we may not have at the time we have to choose an option.
The arguments for and against the various alternatives are usually made along the lines of complexity, flexibility, and time to develop. The simpler solutions take less time to develop, but will be less flexible in the face of potential changes. More flexible solutions are great, except that they take more time to develop. If the expected changes never happen, the extra time to develop the feature is wasted effort.
Since we are not omniscient, we cannot know a priori which design alternative will ultimately be correct. Because we need to make a choice right now, we need some kind of guiding principle to help make a decision.
If I saw a pair debating the best approach for more than three minutes I’d
probably ask them to decide which approach to try first. This leads to useful
considerations, like "Will it be easier to refactor from A to B, or from B to A, if we
change our minds later?" When reminded that the goal is to decide what to do in the
next 5 minutes, not what is "best" in some absolute sense, most pairs move on…
When I talked about this post with my girlfriend Emily, she said “When hanging a picture, it’s better to drill a pilot hole sized to the screw first. If you drill into a stud, no harm done. If you start with a hole sized for an anchor and drill into a stud, you have patching, sanding, and painting to do.” (A stud finder helps in these situations too, but you get the idea.) I’m sure you all have similar examples.
This idea integrates nicely with the principles of YAGNI and Last Responsible Moment. Software, by nature, will change. We are unable to predict how and in what way it will change. Well designed software is not over-engineered software. If we tried to write software that would be malleable in the face of any kind of change, ever, we would never finish. There are simply too many possibilities. It would be an expensive waste of time. The alternative is not to write software that cannot change (otherwise it would no longer be soft-ware), but to make intelligent choices about which axes of change we will support.
In the face of two similar alternatives, we should choose the one that will be easier to change. This will usually be the one that commits the fewest resources, imposes the weakest restrictions, and takes the least time.