Good, Fast, Cheap. Pick two. –unknown
It’s a familiar adage to most developers. Software will be buggy, lack features, or be expensive to develop.
But this is just a project management tool.
Known as the project management triangle (or a few other names), this framework gives project managers a way to approach quality, scope, and cost. Each attribute corresponds to something a project manager controls.
- Good: When can it ship?
- Fast: What’s the scope?
- Cheap: What’s the budget?
Adjusting the ship date, scope, or budget will necessarily affect at least one of the others. This is incredibly useful. Adjustments are often necessary; it helps to know what will be needed to compensate.
But too often this is used to accept the current quality, scope, and cost of software. The project management triangle doesn’t actually say anything about quality, scope, or cost—it’s about the relationship between them. It says that you can always trade between quality, scope, and cost.
Engineers work from a different set of constraints.
From an engineering perspective, quality, scope, and cost are byproducts. Engineers fix bugs and write features. Based on the engineering output, project managers allocate quality, scope, and cost. Those project decisions are byproducts of the engineering work.
Software can be better, faster, and cheaper—if engineers can write it more quickly and/or with fewer defects. If development improves substantially in speed or quality, then project managers can reallocate the savings to improve quality, scope, and/or cost. In other words, software development must improve if we want improved software.
Thankfully, software development improves constantly. And while individual changes are usually not substantial, the cumulative effect is. It’s much easier to develop most applications today than it was even a few years ago. But because software quality is acceptable from the project management perspective, the savings are most often used to increase scope and decrease cost.
Still, if you reflect on today’s best practices, you can hopefully see how they’ve improved development by making it faster or reducing defects.
Automated testing makes code easier to refactor, prevents defects, and saves time by reducing the need for manual testing (by engineers).
Type safety makes code easier to refactor, prevents defects, and helps engineers understand the system.
Iterative development approaches help engineers prioritize the most important work and eliminate time wasted on unnecessary requirements.
Functional architectures like React or Elm increase type safety, testability, and reproducibility.
Libraries like Scientist provide confidence for large changes by proving the results before they’re visible to users.
Faster hardware can lower compile times, reducing wait times and context switching during engineering work.
Platform improvements hopefully increase productivity by adding new APIs and fixing old bugs.
Screencasts provide valuable insights into improved techniques, which should improve the development process.
There are many more improvements—far too many to list.
Software is improved by improving the training, tooling, or processes that drive its development. Better frameworks, architectures, compilers, debuggers, package managers, data modeling, etc. work together to improve software overall. That’s the only way out of the mess of modern software. Training, tooling, and processes must improve to make development faster with fewer defects. Then the savings can be redistributed to improve quality, scope, and cost.
That doesn’t mean that software can only be improved by people who work full-time on training, tooling, or processes.
We all have a responsibility to push our individual teams forward. Don’t accept your current quality, scope, and cost. Question how you can change your development to produce features more quickly and with fewer defects.
Invest in additional training: screencasts, conferences, books. That might mean allocating budget or time. It’s hard to improve your software development without learning about possible improvements.
Establish an architecture for your application that makes adding features easier and writing bugs harder. This probably means adding more distinct and well-defined horizontal or vertical layers.
Create a design language system. Reusing UI means writing less code and debugging less code. It also improves communication between design and engineering.
Automate where possible. Always question whether a computer can do the work you’re doing manually. Building the app for release yourself? Automate it. Deploying manually? Automate it. Wasting time formatting your code? Automate it.
Use tools that catch bugs. This is especially true for automated tools. Thread debuggers, memory debuggers, static analyzers, coverage tools, hotspot analysis.
Find ways to make debugging easier. Maybe you need to add
debugDescriptions to some of your Swift classes. Maybe you need to refactor to make code easier to understand. Maybe you need better testing environments or test data.
Make testing easy. Ideally it’s easier to verify code with a unit test than by manually running the software. This usually means providing good fixtures or data generation, adding custom assertions, and/or refactoring code to separate logic from side effects.
Most importantly, approach development as something that can improve. Listen to feedback, reflect on your performance, and question how you can do better. Don’t be satisfied with the current state of the industry or of your team.
We all need to continually strive for better ways to write software. That’s the only way we’ll get better software.