I love elegant code—software that’s easy to understand and maintain—but it’s not just my aesthetic sense of beauty that attracts me to elegant software. It’s also my pragmatic sense for being economical and building code whose cost of ownership is as low as practical.
I used to feel confused as a software developer because it seemed like there were so many ways of approaching problems and so many different technologies you could use. I wasn’t sure what criteria to use to evaluate what good software is. In fact, the question of what good software is really didn’t occur to me until a long time into my career.
And I’m not alone. I ask many developers the same question about what good software is and they have a hard time answering me. Of course, we know we want our software to be reliable, dependable, and maintainable, but few developers know specifically what we must do and what we must avoid when writing code in order to make it reliable, dependable, and maintainable.
I used to feel that the criteria for good software might be different from project to project, but now I realize that it’s always cheaper to build quality software, and we’re deluding ourselves if we think otherwise. There’s a false assumption in software management that the right way to do things is the quickest way so that you can get a feature out the door and to a customer as soon as possible. That can be a worthy goal, but it often translates into cutting corners and compromising code quality in ways that slow projects down, both in the long term and in the short term.
Software development is a discovery process. We may think we’ve been hired to write code, but really, code is an embodiment of understanding. The real test—the thing that makes a great software developer—is our ability to understand what it is we’re automating. Code comes from that understanding, and it emerges through a discovery process.
Many successful corporations have been challenged by their IT department, not because they’re lousy developers, but because IT is difficult and different from other departments in most corporations. Rather than breaking down features into manageable tasks, many software projects batch features into releases and then try to figure out the whole design up front. Software development projects have to be managed differently from other kinds of projects.
I’m always a bit surprised when I meet development teams where the managers seem to distrust their developers. I know that we developers have a tendency to overbuild, or “gold-plate,” as we call it. This is frequently due to not knowing exactly when we’re done and not knowing how robust a feature needs to be. Often, though, a quick conversation with the product owner or customer about the relative priority of a feature enhancement over other features in the system will help developers gain the clarity they need.
Agile developer practices and emergent design propose that rather than figuring it all out up front, it’s a far more cost-effective way of building software to figure things out as we go. As it turns out, it’s also a better way of producing higher-quality solutions than the standard waterfall approach.
Developers tend to gold-plate code when they’re unclear how it will be used. We don’t want our code to break in the field, so we try to make it as robust as possible. But without knowing the context for the code, we can overbuild and waste valuable time.
And while we want to be proud of the work we do, we also want to bring the most value to our customers. We should be open to changing gears on a project as long as what we’re delivering is in reasonable shape.
In agile software development, we partner with the customer and together discover the best way to build software for their needs. This collaboration is far more effective than the “throw it over the wall” approach of waterfall development because it allows for continuous feedback. But we also need mechanisms to assimilate that feedback.
Acceptance test-driven development (ATDD) is a valuable practice for building needed features without overbuilding or gold-plating. With ATDD, we start by defining acceptance tests for the features we want to build. Using an acceptance test-driven framework like SpecFlow, Cucumber, or Fit, we can automate the acceptance tests to run against our code and validate that our code continues to work as expected. This lets management see at a glance which features are done and passing their acceptance tests and which ones are left to do. This also gives feedback to the developer that the acceptance criteria are fulfilled and the feature is done.
When it comes to avoiding gold-plating, acceptance tests are a great way to help. When the acceptance test, which is defined by the customer or product owner and the team, passes, then it means we’re done building that particular feature. This helps us gain clarity on the work we’re doing and keeps us focused on building the most important bits of the system. Most importantly, when the acceptance test passes we get clear feedback from the system that the feature is finished, and we can move on without fear of missing something.