Writing Clean Code — How It Impacts the Future of Your Product
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.
There are several anecdotes from big tech companies on clean coding strategies, improvements, or impacts on the business. Here are a few examples:
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?
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:
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.
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 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:
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.
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.
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.
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:
- Breaking of the linear flow
- 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.
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:
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.
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:
- Methods that do too much or have a high degree of cognitive complexity cannot be easily, either with unit tests or System Integration Tests.
- 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.
- 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.
Software Testing and Quality Assurance: A Modern Analysis of Its Internal Dynamics and Impact on Delivery
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.
Software Delivery Value Chain: Unveiling the Key Challenges and Opportunities for Successful Delivery in Today’s Market
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.
- Invest in a solution design document.
- 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.
- Articulate a “Definition of Ready” for user stories and discourage developers from implementing user stories that are not thought through.
- 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.
- Allocate a portion of resources for Research and Development (R&D). This investment must also cover the infrastructure, process, and code improvements.
- Commit to one major upgrade every 1-2 years.
- 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
- Make sure code review is an integral part of your SDLC
- Leverage enterprise collaboration tools such as JIRA, GitLab, or BitBucket for implementing proper code review processes.
- Publish code styling rules and guidelines (Google’s style guides include various topics and languages–an excellent place for inspiration).
- Train new staff on product development processes and industry best practices.
- Ensure code reviews happen against a well-described and documented design in user stories.
- 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
- Publish a test strategy explaining how the different tests (unit, system integration, or other tests) should be used.
- Develop a test automation framework that is suitable for your team and product.
- Invest in automation (build, test, deployment).
- 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
- 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.
- Build frameworks around necessary application modules. Frameworks provide structure and patterns that developers can rely on in new implementations.
- Encourage collaboration between architects and developers when designing new functionality or modifying architecture.
- Hire architects and product owners that understand the business and the product and implement the necessary processes to oversee significant design decisions.
- Make sure new functionality aligns with the product’s overall software architecture.
- 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
- Get inspiration from online content (Google Styling Guide is a great example).
- 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