Writing Clean Code — How It Impacts the Future of Your Product

1. Overview

Operational excellence is critical for delivering high-quality products quickly. However, achieving operational excellence is not possible without clean code. Clean code is the foundation upon which a successful software product is built. It is essential for ensuring the quality and speed of project deliveries, two critical factors in your value proposition.

  • Clean code is vital because it impacts the entire development process. From the earliest stages of a project, clean code can help developers build a strong foundation that can scale and evolve as the product grows. It can improve development speed by making building, testing, and deploying code changes easier. It can also reduce maintenance costs by making it simpler to modify or extend existing code.
  • Moreover, clean code is essential for achieving operational excellence, which is the key to delivering high-quality products. When code is clean and well-structured, it is easier to identify and resolve issues quickly. This, in turn, allows developers to deliver high-quality products faster and more efficiently, giving the organization a competitive advantage.

There are several anecdotes from big tech companies on clean coding strategies, improvements, or impacts on the business. Here are a few examples:

  • Google is known for its focus on clean code and has developed a set of programming guidelines called “Google Style Guides.” These guidelines cover various topics, from formatting and naming conventions to code organization and documentation. Google engineers are expected to follow these guidelines rigorously. The company has found that this approach leads to more efficient and effective development, with fewer bugs and less technical debt.
  • Amazon’s continuous improvement culture extends to its approach to clean coding. The company encourages developers to take ownership of their code and constantly look for ways to improve it. Amazon also strongly emphasises code reviews, with every line of code that goes into production undergoing rigorous scrutiny. This approach has helped Amazon maintain a high code quality level, even as its codebase has grown exponentially.
  • Microsoft has invested heavily in tools and processes to support clean coding practices. One example is their Code Analysis tool integrated into their Visual Studio development environment. This tool automatically analyzes code as it is written, providing real-time feedback on potential issues such as code duplication, security vulnerabilities, and performance bottlenecks. This approach helped Microsoft identify and address potential issues early in the development process, leading to more efficient and effective development.

By prioritizing clean code and investing in tools and processes to support it, companies can improve the efficiency and effectiveness of their development, reduce technical debt, and ultimately deliver better products to their customers.

In this article, we’ll explore the importance of clean code in achieving operational excellence. We’ll discuss how clean code impacts the development process, the quality and speed of project deliveries, and a product’s value proposition. We’ll also provide practical tips for writing and maintaining clean code. By the end of this article, you’ll understand why clean code is vital in software development and how it can help you achieve operational excellence.

2. What’s Clean Code?

2.1 Clean Code Is Easy to Read, Understand, and Maintain

Clean code is code that is easy to read, easy to understand, and easy to modify. It is well-structured, well-documented code and follows established coding conventions. Clean code is different from messy or poorly written code in several ways.

Naturally, it is best if all your codebase is clean and tidy, but the reality is messy, which is rarely the case. So how do we reconcile a messy reality with a desire to keep the product tidy and orderly?

Quality vs Effort: To achieve topmost quality, developers might have to invest inordinate effort as they reach higher quality targets. This is sometimes not viable from a commercial point of view as returns significantly diminish despite the increasing costs.
Quality vs Effort: To achieve topmost quality, developers might have to invest inordinate effort as they reach higher quality targets. This is sometimes not viable from a commercial point of view as returns significantly diminish despite the increasing costs.

In his seminal work on the topic, Adam Tornhill argues that not all technical debt needs to be eliminated, especially when it does not make sense financially. We can say the same for low-quality code. Tornhill’s analysis consistently showed that only a tiny fraction of files in a code base are touched by new features or bug fixes (changes follow a Pareto distribution). It is generally sufficient to handle files forming such bottlenecks only (through what he refers to as Behavioral Analysis).

So, where exactly do we draw the line on quality and are there any simple actions to keep low-quality code under control? This article will attempt to answer these questions in some detail. The discussions will revolve around the following ideas:

  • What is poor quality vs clean code — Under this section, we look at software design, productivity, testability, and extensibility.
  • How to write clean code — here, we investigate code readability, cognitive complexity, and coding style.
  • Long-term implications of poor code — this discussion will revolve around increasing and uncontrolled technical debt and how it can turn a product from an asset into a liability.

2.2 Examples of Clean Code vs. Messy Code

Let’s take a look at some examples of clean code vs. messy code to illustrate the difference:

def calculate_square(n):
    """
    Calculate the square of a number

    Args:
        n (int): The number to calculate the square of

    Returns:
        int: The square of the number
    """
    return n ** 2

Messy code example:

def sq(n):
    x=n**2
    return x

In the clean code example, the function is well-named, well-documented, and easy to read. In contrast, the messy code example uses a poorly named function and variable, lacks documentation, and has unnecessary complexity. The clean code is much easier to understand and modify, making it a better choice for any project.

3. Causes and Origins of Poor-Quality Code

Poor-quality code can arise from various factors, including inconsistent coding styles, steadily increasing technical debt, poor software architecture and design, obsolete development methods, lack of coding skills, and deficient process governance.

Top reasons why developers write messy code.
Top reasons why developers write messy code.

3.1 Inconsistent Coding Styles

One of the leading causes of poor-quality code is inconsistent coding styles. Different coding styles can make reading and understanding the code difficult for other developers. This can lead to bugs, maintenance issues, and delays in development.

3.2 Steadily Increasing Technical Debt

Technical debt refers to the cost incurred when compromising quality for faster delivery. This compromise is sometimes unavoidable as project constraints might compel developers to deliver poorly-designed or programmed features. Usually, such implementations are earmarked for refactoring in upcoming technical debt reduction exercises.

However, insufficient resources or deprioritisation of refactoring exercises might prevent development teams from addressing technical debt. Developers might then unconsciously be encouraged to lower their development standards, leading to even more poor-quality code.

3.3 Poor Software Architecture and Design

Another common cause of poor-quality code is poor software architecture and design. When software is poorly designed, it can lead to complex, difficult-to-maintain code that is prone to bugs and issues. Additionally, poor architecture can make it challenging to add new features or modify existing ones.

3.4 Obsolete Development Methods

Obsolete development methods, such as those not leveraging Test-Driven Development, code review, and poor unit testing practices, can lead to poor-quality code. These methods make it difficult to catch bugs early in the development process, which can lead to more time spent on debugging and maintenance.

3.5 Lack of Coding Skills and Drive for Operational Excellence

Lack of coding skills and little drive for operational excellence can also contribute to poor-quality code. When developers lack the skills and knowledge necessary to write clean and efficient code, it can lead to bugs, maintenance issues, and delays in development.

Additionally, there may be little incentive to improve code quality if there is little drive for operational excellence and continuous improvement within a development team or organization. Usually, by the time such become visible to top management, they would have become overwhelming and extremely challenging.

3.6 Deficient Process Governance

Deficient process governance can also contribute to poor-quality code. Development processes can delay delivery when they are not well-defined, well-managed, effective, or weakly enforced. This problem becomes more acute when multiple teams with their own coding styles, standards, skills, and objectives work on the same codebase.

3.7 Legacy Code with Poor Documentation and Lack of Familiarity with the Product

Legacy code with poor documentation and a lack of familiarity with the product can also contribute to poor-quality code. When developers are unfamiliar with the codebase or the product, and when developing quality code is overwhelmingly costly, they will prioritise speed of delivery over quality.

4. State of Software Development

Software development has changed a lot since it kicked off in the 1950s and has expanded exponentially and in every direction.

The challenges of developing quality software code lie in the presence of many competing technologies and programming languages and the fantastic rate at which software engineers are in demand.
The challenges of developing quality software code lie in the presence of many competing technologies and programming languages and the fantastic rate at which software engineers are in demand.

The technological expansion of software has been so fast that coding standards and best practices have trouble keeping up.

Bob Martin recounts an interesting story in many of his talks. It goes like this: The number of software developers worldwide doubles every five years, so looking at a random sample of software developers, chances are that half the population will have less than five years of experience in development!

This fact is remarkable as five years is just enough time for software developers to acquire the necessary skills, significantly since they will also move up the corporate ladder sooner or later. These two facts make it harder to find experienced developers.

Under these conditions, users today are happy to have applications that run just about right instead of perfectly all right.

5. Benefits of Writing Clean Code

Writing clean code has several benefits, including:

  • Readability: Clean code is easy to read, making it easier for other developers to understand and modify. This is particularly important in collaborative projects or projects where multiple developers may need to work on the same codebase.
  • Maintainability: Clean code is also easier to maintain. When code is well-structured and follows established conventions, it’s simpler to identify and fix issues. This reduces the overall time and effort required to maintain a codebase.
  • Scalability: Clean code is also more scalable. When code is well-structured, it’s easier to add new features or modify existing ones. This is important in projects expected to grow or evolve.
  • Efficiency: Finally, writing clean code is more efficient. Clean code is easier to read and understand, reducing the time required to make changes or fix issues. This can improve the overall development speed and reduce maintenance costs.

Overall, writing clean code is critical for ensuring the success of a software project. It leads to better readability, maintainability, scalability, and efficiency, making delivering high-quality products more manageable and effective.

6. Quality Code

Quality has some qualitative aspects that are hard to measure, but they can, at least, be enumerated and defined. While our choice of metrics with which we could measure code quality could acquire many forms, we found the following three criteria to cover most of what we would like to see: productivity, extensibility, and testability. Let’s examine these criteria one by one.

Code Quality Assessment Criteria: Productivity, Testability, and Extensibility.

6.1 Productivity

Productivity as a function of Code Readability and Accessibility
Productivity as a Function of Code Readability and Accessibility

Productivity refers to how easy or difficult it is to maintain the current code. Two factors primarily impact productivity as far as coding is concerned: readability and accessibility. Productivity in this context focuses on the analysis and coding stages (not the entire software delivery chain), measuring how easily a developer can read and interpret a piece of code.

6.1.1 Readability

Code is like humour. When you have to explain it, it’s bad.

Cory House

Short and explicit statements are easier to believe than those that are hard to read. This phenomenon is called cognitive ease and is one of many cognitive biases our minds possess. Should we deliberately strain our cognitive faculties by writing hard-to-read code so developers will spend more effort making sense of it? I believe that would be counterproductive.

A better approach, in my view, is to make code more legible so that developers can focus their thoughts on understanding and solving the problem. But how do you make code more legible? Some people like to see the whole function on a single page; others would like to see enough white spaces and an aesthetically pleasing structure.

Most people would agree on consistency in style, which is where style-checking becomes an essential aspect of your development processes and, therefore, must be included in your IDE or continuous integration pipeline.

6.1.2 Accessibility

Two factors influence accessibility: cognitive complexity and language-specific skills.

Cognitive complexity (cyclomatic complexity’s successor) is a mathematical measure created by G. Ann Campbell and used in SonarQube to detect complex code. This measure scores a piece of code based on criteria that would make it either hard or easy to interpret. These criteria have more to do with the implemented logic or algorithm and less with the programming language. Examples of these criteria are:

  1. Nesting
  2. Recursion
  3. Breaking of the linear flow
  4. Operator sequence

As for the second factor, language-specific skills, recruiting developers with expertise in the language immediately augments accessibility. One of the principles of Operational Excellence is using mature and proven technology where experienced developers can be quickly recruited and online material easily found.

Of the many mature technologies available today, some are low-level while others are high-level. Low-level programming languages require more expertise and lines of code per feature to achieve the same result. One of Google’s early engineering decisions is “Python where you can, C++ where you must“, and a very wise one.

The ultimate objective of developing software is to create business value for your customers. The latter may not care which programming language is used or how fast it runs as long as it solves their business needs cost-efficiently.

You also want to acquire a winning value proposition. There is little point in creating a perfect product that never reaches your customers.

6.2 Extensibility

Extensibility as Sound Architecture and Low Technical Debt

Extensibility measures how easy it is to extend the functionality in a piece of code. Two elements govern the ease with which software code is extensible: good architecture and low technical debt.

6.2.1 Software Architecture and Design Patterns

Software design patterns fall into four categories: structural, creational, behavioural, and concurrency, and serve as textbook answers to known problems. For example, you would use a state machine pattern to implement parcel tracking software, where packets move between well-defined states (like Ordered, Shipping, Delivered).

The subtle relationship between design patterns and software architecture can be understood as follows:

  • Software design patterns are universal, and every experienced developer must know how and when to use them. They are designed in a way that allows functionality to be naturally extended.
  • Software architecture can never be wholly defined top-to-bottom unless all the business requirements are known at design time. This is hardly ever the case, and users only know what they want after seeing what the technology can offer. Therefore, software architecture emerges as new code is added, and design patterns are perfect for introducing orderliness into the programming process.
  • Appropriately implemented, software design patterns are crafted using programming best practices, like the SOLID principles. They provide developers with flexible boundaries within which good architecture can emerge.

6.2.2 Low Technical Debt

Technical debt is the amount of work you leave behind when you sacrifice software quality for faster delivery. Sometimes, these sacrifices are justifiable, making technical debt a problem you must manage and control rather than eliminate. Technical debt can be kept under control by refactoring, a process devised by Martin Fowler in 2000 with contributions from notable experts like Kent Beck:

Refactoring is a controlled technique for improving the design of an existing code base. Its essence is applying a series of small behaviour-preserving transformations […] the cumulative effect of each of these transformations is quite significant. Doing them in small steps reduces the risk of introducing errors. […] which allows you to gradually refactor a system over an extended period.

Cory House

A test suite that gives you enough confidence to perform the exercise is a prerequisite to refactoring large pieces of code. Technical debt beyond a certain threshold would become too expensive, disruptive, and challenging to manage. This is when you get legacy code. It is much easier and far less risky to refactor code constantly.

6.3 Testability

Testability is a measure of how easy it is to test new code. Many factors can influence testability, but our primary focus is on code-specific ones:

  1. Methods that do too much or have a high degree of cognitive complexity cannot be easily, either with unit tests or System Integration Tests.
  2. Not enough modularity so that components can be isolated and any dependencies accounted for. In this case, considerable mocking is required, raising the product’s cost of ownership and the cost of change.
  3. The new code is buried behind layers of other code and is not accessible externally through APIs, for example. While this is not necessarily a faulty design, it makes any new code challenging to test.

These issues are less prominent if you use Test-Driven Development or code with automation in mind. Both approaches will push developers to write code that can be easily tested. Code just developed must be ready to be tested; otherwise, if you leave it till later, chances are it will accumulate and eventually get dropped. Quality code should come with a complete suite of test cases.

7. Consequences of Bad Code

Recognizing the long-term impact of poor-quality code on the product and the business is vital. Equally important is recognizing which factors play a role in the assessment and which do not.

7.1 High Cost of Ownership

The high cost of ownership can result from the convergence of many factors, such as poor production processes, a non-lean value chain, lack of talent and skill, legacy products, and low-quality code.

When discussing Waterfall and large software project management, Winston Royce rightly stipulates that customers are happy to pay only for analysis and coding as this creates business value. Everything else, from unit testing to documentation, deployment, and bug fixing, are costs to be avoided. Businesses must develop quality code to maintain a low cost of ownership.

7.2 Decreasing Value Proposition

The margins between competing firms are usually not wide enough for slacking value propositions, and the latter must be demonstrable.

Flawless deliveries enhance your firm’s market reputation and bring about more business. You can have the best people and processes. Still, if your solution requires more time and budget to implement because of obsolete technology or legacy code, you will soon be unable to compete.

7.3 From Asset to Liability

Naturally, some products in a company portfolio will perform poorer than others, but this is not the same as when a product turns from an asset to a liability. Technical debt and poor quality can slowly erode your software delivery pace, making projects less profitable.

Poor-performing products cannot be quickly abandoned without an appropriate replacement strategy. This might leave the clients in a difficult position and open the door for competitors to place a foot in the door.

8. Guide to Maintaining Quality Code

This section will provide some guidelines on producing and maintaining quality code. The reader should note that the below is not a prescription and may vary between teams, industries, and organizations. The general assumptions, however, remain valid.

8.1 Investing in Design

8.1.1 What it means

The first sacrifices are usually design and documentation when software teams are pressed for time. But that is not ideal. Regardless of the deadlines, guarantee a minimum level of design and documentation; these things are notorious for biting back!

Proper design by experienced architects and product owners eliminates the potential of introducing unsustainable code, unusable features, or unreliable solutions. Design investments become more important when projects are large or introduce new changes that have a system-wide impact.

Investing in design keeps the product foundations rock-solid while preventing quick-and-dirty fixes and short-term hacks from creeping into the codebase.

8.1.2 How to Do It

Use the following guidelines to prevent poor design from impacting the quality of your code.

  1. Invest in a solution design document.
  2. Follow the 70-30 rule for design/development efforts. A product owner documents the design decisions and implementation recommendations in their user story. Developers then must understand the requirements and the recommended solution design before commencing the implementation.
  3. Articulate a “Definition of Ready” for user stories and discourage developers from implementing user stories that are not thought through.
  4. Architects and developers should review the design for feedback and buy in. This is especially important when solutions have a downstream impact on other systems.
  5. Allocate a portion of resources for Research and Development (R&D). This investment must also cover the infrastructure, process, and code improvements.
  6. Commit to one major upgrade every 1-2 years.
  7. Encourage constant (but clever!) refactoring of old code to reign in technical debt.

8.2 Diligent Code Review

8.2.1 What it means

Code review ranked number 1 in a survey on maintaining quality code. Its main objective is to review code against internally published guidelines, industry best practices, and programming standards.

Code styling is an important area to review. A Code Style comprises an extensive set of rules governing the writing style (commenting, naming, indentation, spacing, vertical alignment, indentation, usage of global variables or static functions, and so on). Having an internally published Code Style to help keep the code consistent across the board.

8.2.2 How to Do It

  1. Make sure code review is an integral part of your SDLC
  2. Leverage enterprise collaboration tools such as JIRA, GitLab, or BitBucket for implementing proper code review processes.
  3. Publish code styling rules and guidelines (Google’s style guides include various topics and languages–an excellent place for inspiration).
  4. Train new staff on product development processes and industry best practices.
  5. Ensure code reviews happen against a well-described and documented design in user stories.
  6. Ensure the code review does not focus solely on the less critical aspects of the implementation, such as styling. It should focus more on the application’s business logic and generic design principles.

8.3 Testing and Automation

8.3.1 What it means

Regression test suites with decent coverage make constant refactoring of old code practical by eliminating fears of breaking existing functionality. Test automation compounds the advantage of regression test suites by providing a mechanism for shorter and more reliable feedback.

Automated software testing is, in my view, essential for Agile practices. When writing new code, make sure to add the necessary test cases. Do not commit code that does not have unit or system integration tests.

8.3.2 How to Do It

  1. Publish a test strategy explaining how the different tests (unit, system integration, or other tests) should be used.
  2. Develop a test automation framework that is suitable for your team and product.
  3. Invest in automation (build, test, deployment).
  4. Embrace some form of Test-Driven Development (TDD)

8.4 Sound Architecture

8.4.1 What it means

See the previous section on software architecture to understand its impact on quality code.

8.4.2 How to Do It

  1. Use Other People’s Experience (or OPE) when implementing solutions for existing problems. Choose standard, time-tested architecture and design patterns suitable for your product requirements.
  2. Build frameworks around necessary application modules. Frameworks provide structure and patterns that developers can rely on in new implementations.
  3. Encourage collaboration between architects and developers when designing new functionality or modifying architecture.
  4. Hire architects and product owners that understand the business and the product and implement the necessary processes to oversee significant design decisions.
  5. Make sure new functionality aligns with the product’s overall software architecture.
  6. Constant refactoring and investment in R&D help keep the product up-to-date with the latest technological standards.

8.5 Publish a Styling Guide

8.5.1 What it means

Publish an internal Styling Guide that everyone can read and follow. Make sure it’s part of the code review process. Code Styling should be geared for maximum readability.

8.5.2 How to Do It

  1. Get inspiration from online content (Google Styling Guide is a great example).
  2. Ensure it’s part of the code review process.

9. Final Words

In conclusion, writing clean code is not just good practice; it is essential for the long-term success of a software product. The quality of code impacts not only the efficiency of development but also the reputation and profitability of the business. As such, it is critical to prioritize clean code in every aspect of software development. Developers can achieve operational excellence and deliver better products in shorter timeframes by adopting better coding disciplines, embracing efficient design, and leveraging better testing techniques.

It is crucial to view producing quality code as a collective effort enforced through internal policies and processes rather than a discretionary exercise left to individual staff members. Only by prioritizing clean code can software development companies achieve their objectives and thrive in today’s competitive business landscape.

10. Further Reading

  • An excellent article on modular design can be found here.
  • Great content from Bob Martin on clean code.
  • Another set of great videos that will help you start with design patterns

Leave a Reply

Your email address will not be published. Required fields are marked *