When is it a Good Idea to write Bad Code?


Imagine an asteroid is barreling towards earth and the head of NASA tells you and your development team that they have 12 months to code the guidance system for the missile that will knock the asteroid off it’s earthbound trajectory.

Your first thought may be, “Sh$@*t!”  But let’s say you agree.  What does the quality of your code look like after 12 months?  Is it perfect?

Now imagine an asteroid is barreling towards earth and you have only 1 month to code the guidance system.  Now, imagine a worse scenario, you only have 24 hours to code the guidance system or the earth is a goner.

What is Technical Debt?

You should read Martin Folwer’s excellent introduction to technical debt.  Technical debt could be defined as the difference between the application that you would like to build and the application that you actually build.

All software projects with limited resources will accumulate technical debt.  

Technical debt is often viewed as a negative or pejorative term.  However, in reality, technical debt can be leveraged to actually launch successful applications and features.  The key to leveraging technical debt is to understand:

  • What types of technical debt can you afford to accumulate?
  • Where in your codebase can you afford to accumulate technical debt?
  • When do you need to make a payment on your technical debt (when to refactor)?
  • Application development typically involves a variety of groups and individuals, for example the development team, business stakeholders, and your end users.  To understand technical debt, it’s important to realize the following:

    Technical debts are always paid by someone.

    Types of Technical Debt

    To understand technical debt, it is important to understand the different types of technical debt that can accumulate in an application.

    Missing Functionality

    Missing functionality can make the user experience of your application more awkward to use, but on the up side it doesn’t pollute the codebase.  Chopping features can be a useful technique for reaching deadlines and launching your application.

    Tradeoffs

  • User experience can suffer from missing functionality.
  • Overall utility of your product or service may be compromised.
  • Future development will be more costly, if the feature must be added later.
  • Broken Functionality

    Broken functionality refers to code that “straight up” does not work under some or all circumstances.

    Tradeoffs

  • Possible increased risk of catastrophe (system failure, security compromise, data corruption, etc.)  Sometimes code can cause serious damage to people and systems.
  • User experience may suffer as users run into bugs that inhibit their ability to use your application.
  • Utility of your application can decrease significantly.
  • Plan on adding the broken functionality to your bug list, so future development will suffer.
  • Bad Architecture Design

    Systems benefit from being loosely coupled, and highly independent.  Conversely, highly coupled systems with lots of dependencies or interdependencies can be highly erratic and difficult to maintain and upgrade.  They can also be very difficult to reason about.

    Bad architecture decisions can be some of the most expensive to fix, as architecture is the core underpinning of your application.

    Tradeoffs

  • Certain solutions may not scale with traffic based on their initial design or could be cost prohibitive to effectively scale in the future.
  • Logical complexities, for instance in the case of a poorly designed database, can ripple through the entire application.  Making future development slow to a crawl.
  • Bad User Experience Design

    If you’re users don’t know how to user your application or make mistakes while using your application because of an overly complex user interface, than it will surely be your user base that is paying down this form technical debt.

    Bad user experience design can be very expensive to fix since the whole of the application development process depends on it.  Leaving the very real possibility that you’ve spent your limited development resources creating subsystems and code that don’t properly solve the problems of your end users.

    Tradeoffs

    • Decreased utility as your users struggle to fully take advantage of the application.
    • Very complex refactorings, since user interface inherently tend to be highly coupled, so again more possible drag on future development.

    Lack of Testing

    The main purpose of testing, both automated and manual is to answer the simple question, does your code work?  If you can’t answer this question, then you’d be better off heading to the casino rather than launching your application.

    In a lot of ways, “lack of testing”, can be viewed through the lens of understanding what you’re application is capable of.

    Tradeoff

  • Maintaining the quality of an application is not possible if you can’t answer the “does it work?” question effectively and efficiently.
  • Future development can be more difficult without a way to tell when functionality stops working or when new features break old ones.
  • Bad Code Readability

    This is usually referred to as code smell, and again I like Martin Fowler’s explanation.  Bad code readability is easy for a developer to spot, because they simply look at the code and decide if it makes sense to them or not.  Therefore it can also be subjective.

    Being able to read and understand code is critical to being able to debug and modify it later.  This should not be confused with broken functionality, which is a different concept.

    In developer circles, there is a somewhat innate bias towards overemphasizing this form of technical debt (the reason being that it is the developer, that will ultimately pay for it).  Just remember that you can have “Bad Code” that doesn’t smell (silent but deadly).

    Tradeoffs

  • Future development can become much more difficult, if not impossible (rewriting working code is common if the readability is bad enough)
  • Risk of being highly dependent on the original developer/team for information about the system (see the bus factor)
  • Loss of the ability to understand your entire application, what it does and how it works.
  • All applications will have some level of technical debt in each of the above categories, but again, it is in managing this debt that gives a development team the ability to successfully launch their application.

    HTML5 Video: Transcoding with Node.js and AWS