Failing Faster and Iterating with Modern Software Development Practices
Nick Scialli • May 26, 2020 • 🚀🚀 7 minute read
Using modern software development practices enable us to find and fix bugs faster and iterate. This post offers a primer into some of these modern practices.
1. Version Control
Use version control on your code (e.g., git). This allows for mobility, collaboration, backup, and enables all sorts of other modern web development practices.
How version control helps you fail faster and iterate
Version control enables pretty much all other quality control measures. It allows you to implement hooks to force things like code testing and linting when you push your code remote repositories. You can hook your code repository up to Continuous Integration servers. The bottom line: modern software development starts at version control!
2. Automated Testing
Automated testing allows you to develop with confidence. They serve as a form of documentation as they assert what your app should do. Automated testing also helps you refactor without fear since you are relatively confident in your new code if tests pass.
How automated testing helps you fail faster and iterate
You can write your code and “smoke test” it, meaning you try playing with your app a bit to make sure it works right. But how do you know your users won’t hit a weird edge case that blows up the app? You don’t, but you can help mitigate this concern by writing automated tests. Once written, these tests live forever, meaning less concern about software regression since your exists tests will (hopefully) fail if your code regresses.
As a bonus, you can use version control hooks and/or Continuous Integration to require your tests to pass before you can move forward in deploying your code.
3. Continuous Integration
CI allows you to merge your code into a single repository frequently. A CI server can run automated tests and other checks to make sure your code is ready to be merged and, in conjunction with your version control service, can be a quality gate.
How CI helps you fail faster and iterate
When you push your code to a remote repository, you can know immediately if your quality measures (e.g., tests, linting) have failed. This is a whole lot better than finding out because a user has emailed you that something is broken!
4. Continuous Delivery/Deployment
Continuous delivery means you continuously deliver new code into a releasable state, ready to be sent to production at the click of a button. Continuous deployment goes one step further and deploys code, usually as soon as it’s merged into a branch.
How CD helps you fail faster and iterate
Making it easy to integrate really small changes into your production environent is critical for iterating. The truth is that you will merge some breaking changes into production. Software development is error prone and we all ship bugs. However, if we ship really small chunks of code to production, our lives become much easier when we eventually do ship a bug. First, if you need to revert your production code, it’s much easier to do so with a very small change set. Second, when you need to figure out what went wrong, you have a pretty small amount of code to investigate. It’s much easier to find a bug in a couple days worth of code than a couple months worth of code!
5. Intellisense and Linting
Use a code editor that has language services for your language. Ideally it provides autocomplete and lints your code. It highlights any compilation, syntax, and lint rule issues real-time as you’re writing code.
How intellisense and linting helps you fail faster and iterate
Features like autocomplete and code highlighting eliminates a lot of bugs as you write them. This may be the fastest way to fail and therefore it might be the most important item on the list! Linting keeps your code consistently-formatted, which makes it a lot easier to read for things like code review and facilitates identifying bugs.
6. Feature Flagging
Feature flags are variables in your code that hide functionality that’s in development from your users. It allows you to keep releasing code as new features are mid-development, meaning those features don’t block continuous delivery.
How feature flagging helps you fail faster and iterate
Releasing constantly is a cornerstone of failing faster. Without feature flags, we might hold off on shipping code for a while because we don’t want to ship a half-baked feature. Feature flags let us ship constantly without worrying about these not-quite-done features!
7. Instrumentation and Monitoring
Use a service like New Relic or Datadog that monitors your app for errors. Set up notification thresholds that notify you or your team when a particularly high number of errors are encountered.
How instrumentation and monitoring helps you fail faster and iterate
Let’s say we ship a change that causes our web server to start spewing 500-level errors. We don’t necessarily want to wait until a bunch of users complain—monitoring tools let us know immediately when we’re getting a high level of abnormal responses. We can then investigate and mitigate the situation before too many users are affected.
Reduce concerns about different environments and simplify deployments by using a containerization service (e.g., Docker). If an app can run locally in a Docker container, it can run deployed on a server as a Docker container.
How containerization helps you fail faster and iterate
Containerization allows us to be working locally in an environment (pretty much) identical to our deployment. This increases the chances that we detect any environment-related issues locally rather than in our production environment. We like figuring out issues locally rather than in production.
Failing faster enables us to find and fix bugs faster and ship higher quality code. We should all aspire to ship better higher quality code and ensure a better experience for our users!
Nick Scialli is a software engineer at the U.S. Digital Service.