Note from Mike - Below is the first post in a new series I'll be writing for optimizing software quality under severe time constraints. The series will consist of 4 short posts, with each entry describing different angles to analyze this problem from.
Oftentimes, business needs can accelerate a software project beyond what's comfortable for software developers.
The cause of this conundrum is simple - when creating a new product, the ultimate goal of the business is to generate as much revenue as possible with it. There's a lot of time and effort (read: $$$) that is invested in planning, building, and setting up a marketing and sales pipeline for new software.
For this investment to be worth it, it's ideal for the product to be shipped as quickly as possible. This minimizes the risk involved in the initial investment by generating revenue as quickly as possible. Users can also test the full product sooner and provide helpful feedback for future iterations. A software project that is stuck is an enormous resource drain and risk, and can easily cause a company to fail.
However, quick delivery of software still requires thought for long-term maintenance. If the software developers involved in the project don't plan for this, then technical debt inevitably accrues. Once this happens, and especially if there's developer attrition over time, future iterations become harder and harder to build.
No software developer likes to inherit a shoddy architecture or poorly written code - but this scenario is extremely common in our industry. This is because code architecture and quality is almost always the first thing sacrificed when the business is in a rush. It's easy for us software developers to argue for why we should be given the cycles to ensure sound code quality, but it's not realistic to think that we'll always be building software in perfectly ideal conditions.
While software developers can't entirely eliminate the risk of sacrificing code quality under tight deadlines, there are ways to mitigate it. The first strategy is to streamline the technical aspect of the software development process as much as possible. This means determining strategies to make the total set of code written as expressive and as reusable as possible.
Picking the Right Technical Overhead
When software development is rushed, it's best to only incur overhead that provides immediate gains for developer productivity. For example, configuring a build system can be tedious, especially in front-end development. However, if you take the time to set this infrastructure up properly, you can take advantage of features (such as hot reloading) that accelerate development in your local environments and save you time in the long run.
Even simple things like setting up a logical directory structure or stubbing out example functions/features can provide efficiency gains in that they give software developers the footing to build software faster. Early on, it's important to identify easy wins that will make your codebase setup properly for fast, but organized, development.
A modern trend in front-end development is to use a homegrown component library, to maintain visual consistency and speed up development. These types of strategies, especially if they solve multiple use cases across a codebase, should be examined as they might be worth the cost of setting up the infrastructure needed to support them.
The overarching principle here is that you don't want to encourage cowboy coding, even if your team is in a rush. On greenfield projects, getting proper architecture into place is often worth the investment. But remember, you won't have time to make it perfect - you'll just have to pick and choose which technical overhead you want to take on based on current time constraints. For example, if you're three weeks out from shipping, it's probably not worth spending three full days troubleshooting a specific aspect of your build tooling.
How to Focus Only on the Most Valuable Code
Just as there's no time for perfect architecture during a rushed project, there's probably not time to roll your own anything, either. If there's code that can be reused from elsewhere, leverage it as much as possible. Of course, there's an inherent risk to this, even in the open source community. The infamous NPM left-pad debacle showcases how reusing code creates a level of risk.
However, if the dependencies are trustworthy (and in the case above, NPM resolved the underlying issue entirely), reusing code can be a significant timesaver. The last thing you want to do during a time crunch is burn cycles on reinventing the wheel. You'll want your team to spend the majority of their time on the code that makes your software valuable.
This logic is even starting to gain traction in the business world from the perspective of using off the shelf solutions for non-unique business functions. Whether from the perspective of someone in leadership or as a software developer, the key concept is to focus entirely on value creation, while leveraging stock solutions for everything else.
Once these dependencies are in place, determine whether the workflows can be streamlined even further. For any larger dependencies such as frameworks, this could mean eliminating as much boilerplate code as possible. For the unaware, boilerplate code is code that has to be rewritten in a similar fashion many times in an application, which isn't ideal for a fast-paced project. Thankfully, many libraries and packages aim to reduce boilerplate - for example, on the front-end, redux-actions is a library that reduces the amount of Redux boilerplate code.
Apologizing for a Hack the Right Way
Even after setting up an efficient architecture and reusing as much code as possible, tight deadlines often still force shortcuts to be taken. Maybe a feature has a minor bug or two, or a hacky solution is written somewhere. In these cases, if the code has to be shipped and there's no time left, I'd advocate for what I call a "hack apology". This means explicitly flagging the code with a comment that:
1) Identifies the code as a hack
2) States why it had to be pushed through as-is
3) States what problems it causes or could cause
4) Contains ideas and/or resources for potential solutions
This goes beyond a simple TODO comment in the style of, "TODO - rewrite this code so Bug #493 is fixed". Instead, this strategy provides context for why the code was written, and then a starting point for yourself (or another developer behind you) to address the hack later. Providing this information up-front can save many cycles in the future when a developer is attempting to deduce your hack and/or debug parts of your software.
With an efficient architecture, a preference for existing solutions, and a strategy for contextualizing hacky code, you can begin to mitigate the risk of sacrificing code quality when software needs to be shipped quickly. There are still other angles to address this problem from, though. Next up, Part 2 of this series will examine how to tackle this problem by reducing process overhead for software developers.