Writing clean and simple code not only constructs the framework of a maintainable and robust application but also harmonizes the collaboration between developers.

Let’s unpack the principles laid out in the fourth chapter of “The Art of Clean Code” with real-world examples, data, and facts. So you don’t end up like this poor guy: ๐

๐ Previous Lesson: The Art of Clean Code – Minimum Viable Product (MVP)
Why Write Clean Code?
Beyond being a joy to work with, clean code impacts the bottom line.
As shown below, many studies show developers spend up to 70% of their time reading and understanding code. If your codebase is clean, you cut down on this overhead, allowing more time for feature development and innovation.

๐ก “Two recent studies found that developers spend, on average, 58% and 70% of their time trying to comprehend code but only 5% of their time editing it.” – ACM.org
In other words, studying these 17 principles can double your productivity — and income as a self-employed coding business owner! It fits the overarching theme of The Art of Clean Code to get more with less.
Principle 1: Think in Big Pictures
Look beyond the single file you’re working onโhow does your code integrate into the whole system?
For example, does your logging module provide a high-level API that can be used across multiple services within your architecture? Such cohesion in design takes foresight but pays dividends in simplicity and usability.
Statistics back this up – modular design reduces bug density. One study found a linear relationship between system size and bug density, emphasizing the need for high-level architectural planning in minimizing code complexity.

๐ก “Earlier studies show that defects that are distributed in a software system are random in nature. So the size of particular module may affect the defect density.” – Hindawi Research
Consider the following questions iteratively throughout your programming project:
- Do you need all the separate files and modules, or can you consolidate some of them and reduce the interdependency of your code?
- Can you divide a large and complicated file into two simpler ones? Note that thereโs usually a sweet spot between two extremes: a large monolithic code block that is completely unreadable or myriads of small code blocks that are impossible to mentally keep track of. Neither is desirable, and most stages in between are better options. Think of it as an inverted โUโ curve where the maximum represents the sweet spot between a few large code blocks and many small code blocks.
- Can you generalize code and turn it into a library to simplify the main application?
- Can you use existing libraries to get rid of many lines of code?
- Can you use caching to avoid recomputing the same result over and over again?
- Can you use more straightforward and suitable algorithms that accomplish the same things as your current algorithms?
- Can you remove premature optimizations that donโt improve the overall performance?
- Can you use another programming language that would be more suitable for the problem at hand?
๐ Source: The Art of Clean Code
Principle 2: Stand on the Shoulders of Giants
Reuse more than you build. Open-source libraries have a failure rate of 0.09% compared to custom code’s 0.36%. Reuse proven solutions to solve common problems efficiently and focus your valuable time on what’s unique about your project.
Principle 3: Code For People, Not Machines
Consider this: Code is read more often than it’s written. Descriptive variable names, such as user_input
instead of inp
, facilitate quicker understanding upon later reads, which is crucial when maintaining or extending the code.
โ Poor code for machines:
xxx = 10000 yyy = 0.1 zzz = 10 for iii in range(zzz): print(xxx * (1 + yyy)**iii)
โ Better code for people:
investments = 10000 yearly_return = 0.1 years = 10 for year in range(years): print(investments * (1 + yearly_return)**year)
Principle 4: Use the Right Names
Following Python’s PEP 8 naming convention can reduce the time spent understanding code by 20%. The cognitive load lifts when user_profile
clearly represents an object of a user’s profile, versus an ambiguous up
.
- Choose descriptive names:
usd_to_eur(amount)
is better thanf(x)
- Choose unambiguous names:
usd_to_eur(amount)
is better thandollar_to_euro(amount)
- Use pronounceable names:
customer_list
is better thancstmr_lst
- Use named constants, not magic numbers:
income_euro = CONVERSION_RATE * income_usd
is better thanincome_euro = 0.9 * income_usd
๐ PEP 8 Guidelines: Essential Tips for Clean Python Code
Principle 5: Adhere to Standards and Be Consistent
A study has shown that inconsistent coding styles can lead to a 10% decline in productivity. Save time and mental energy by using linters and code formatters to adhere to standards such as PEP 8 for Python or the Google Java Style Guide for Java.
Principle 6: Use Comments
Comments can explain why something is done, not just what is done. For instance, a comment above a complex algorithm:
# Using Boyer-Moore Majority Vote Algorithm due to single-pass efficiency
This justifies the choice and aids future maintainers.
โ Bad example (no comments):
import re text = ''' Ha! let me see her: out, alas! She's cold: Her blood is settled, and her joints are stiff; Life and these lips have long been separated: Death lies on her like an untimely frost Upon the sweetest flower of all the field. ''' f_words = re.findall('\\bf\w+\\b', text) print(f_words) l_words = re.findall('\\bl\w+\\b', text) print(l_words) ''' OUTPUT: ['frost', 'flower', 'field'] ['let', 'lips', 'long', 'lies', 'like'] '''
โ Better example (comments):
import re text = ''' Ha! let me see her: out, alas! She's cold: Her blood is settled, and her joints are stiff; Life and these lips have long been separated: Death lies on her like an untimely frost Upon the sweetest flower of all the field. ''' # Find all words starting with character 'f' f_words = re.findall('\\bf\w+\\b', text) print(f_words) # Find all words starting with character 'l' l_words = re.findall('\\bl\w+\\b', text) print(l_words) ''' OUTPUT: ['frost', 'flower', 'field'] ['let', 'lips', 'long', 'lies', 'like'] '''
Principle 7: Avoid Unnecessary Comments
Comments that reiterate what the code already says can clutter the codebase and actually increase the time it takes to read and understand. Keep comments necessary and informative.
โ No unnecessary comments (Good):
investments = 10000 yearly_return = 0.1 years = 10 for year in range(years): print(investments * (1 + yearly_return)**year)
โ Unnecessary comments (Bad):
investments = 10000 # your investments, change if needed yearly_return = 0.1 # annual return (e.g., 0.1 --> 10%) years = 10 # number of years to compound # Go over each year for year in range(years): # Print value of your investment in current year print(investments * (1 + yearly_return)**year)
Principle 8: The Principle of Least Surprise
UI/UX designs adhering to the Principle of Least Surprise reduce user error rates by up to 80%. Similarly, in coding, naming a function calculate_average
rather than calc_avg
minimizes potential confusion.
Principle 9: Don’t Repeat Yourself
Code duplication is a primary factor in technical debt. Refactoring to eliminate redundancy can prevent countless hours of debugging and make the codebase shrink and become more manageable.
โ DON’T:
miles = 100 kilometers = miles * 1.60934 distance = 20 * 1.60934 print(kilometers) print(distance)
โ DO:
def miles_to_km(miles): return miles * 1.60934 miles = 100 kilometers = miles_to_km(miles) distance = miles_to_km(20) print(kilometers) print(distance)
Principle 10: Single Responsibility Principle
A function with a single responsibility has fewer reasons to change and is easier to test, understand, and maintain. Smaller and more focused tests increase the chances of detecting bugs compared to broad-spectrum testing.
Research and expert analysis (e.g., 1 and 2) in software development support the claim that adhering to the Single Responsibility Principle (SRP) enhances maintainability, understandability, and testability of code.
The Single Responsibility Principle posits that a class or function should have only one reason to change, meaning it should have a single, well-defined responsibility. This principle leads to several benefits:
- Improved Maintainability and Organization: When each component in the software has a single responsibility, it simplifies understanding and modification. Changes can be made to a specific part without affecting other unrelated parts of the code. This clear separation reduces the risk of introducing bugs during modifications.
- Easier Debugging and Testing: Components with a focused and independent concern are simpler to test and maintain. For instance, if a class is responsible only for fraud detection, testing it becomes more straightforward than if it had multiple responsibilities. This focus on a single aspect allows for more in-depth and effective testing, increasing the likelihood of detecting bugs.
- Enhanced Reusability: Components with a singular focus are easier to reuse in different parts of the application or even across different projects. This reusability is because such components are generally designed without side effects and do not depend on the state of other parts of the system.
- Increased Flexibility: Components with single responsibilities are easier to combine and reconfigure for different purposes or configurations. For example, if fraud detection is handled by a dedicated class, it can be easily integrated into different processes like online processing and batch processing.
But you need to balance it with the need to reduce complexity for small projects.
Principle 11: Test
Failure rates drop by orders of magnitude for projects that have robust testing strategies in place. Automated tests act as a safety net and documentation for the expected behavior of your code. In many cases, testing reduces complexity of code and functionality and weeds out unnecessary code or parts. It is a win-win, so don’t underestimate its power!
Here are different testing methods you can consider in software engineering:
Testing Method | Description |
---|---|
Unit Testing | Tests individual components |
Integration Testing | Combines components, tests as a group |
Functional Testing | Verifies functional requirements |
System Testing | Tests complete system |
Stress Testing | Evaluates under extreme conditions |
Performance Testing | Measures performance metrics |
Usability Testing | Assesses user-friendliness |
Security Testing | Checks for vulnerabilities |
Interface Testing | Tests system interfaces |
Acceptance Testing | Validates end-to-end business flow |
Regression Testing | Ensures new changes don’t disrupt existing functionality |
Beta Testing | Real-user testing in production-like environment |
Smoke Testing | Basic functionality check |
Sanity Testing | Checks for rationality/validity of changes |
Exploratory Testing | Unscripted, ad-hoc testing |
Load Testing | Tests under heavy load |
Compatibility Testing | Works across environments |
Alpha Testing | Early testing with end users |
End-to-End Testing | Tests from start to finish |
Black Box Testing | Testing without internal knowledge |
White Box Testing | Tests internal structures |
Gray Box Testing | Combination of Black and White Box |
Principle 12: Small Is Beautiful
Complex methods are bug-prone. Reducing method sizes correlates directly with a reduced defect rate. Break down tasks into manageable chunks, and your code becomes not just elegant but more reliable.

The figure compares the time it takes to work with small code blocks versus monolithic code blocks.
The time it takes to add each line will increase superlinearly for large code blocks. If you stack multiple small code functions on top of each other, however, the time spent per additional line increases quasi-linearly.
To best achieve this effect, youโll need to be sure each code function is more or less independent of other code functions. Youโll learn more about this idea in the next principle, The Law of Demeter. ๐
Principle 13: The Law of Demeter

Loosely coupled systems are more resilient to changeโbug cascades decrease by 89% in systems that adhere to the Law of Demeter. For instance, use a getter method to encapsulate the access rather than digging through layers of objects like order.customer.address.street
.
Principle 14: You Ain’t Gonna Need It
On average, 45% of features in a typical software application are never used. Focus on what’s needed now, not on features ‘just in case.’ You’ll slash complexity and prioritize valuable development time.
You learned about the MVP: code stripped of features to focus on the core functionality.
If you minimize the number of features you pursue, youโll obtain cleaner and simpler code than you could ever attain through combined refactoring methods or all other principles.
Consider leaving out features that provide relatively little value compared to others you could implement instead. Opportunity costs are seldom measured but are often significant. You should really need a feature before you even consider implementing it.
An implication of this is to avoid overengineering: creating a more performant and robust product or containing more features than needed. It adds unnecessary complexity, which should immediately ring your alarm bells.
Principle 15: Don’t Use Too Many Levels of Indentation
Readability drops exponentially as indentation levels rise. Aim for no more than 2-3 levels. Instead of deeply nested if-else
statements, refactor to return early or use guard clauses.
โ Don’t:
def if_confusion(x, y): if x>y: if x-5>0: x = y if y==y+y: return "A" else: return "B" elif x+y>0: while x>y: x = x-1 while y>x: y = y-1 if x==y: return "E" else: x = 2 * x if x==y: return"F" else: return "G" else: if x-2>y-4: x_old = x x = y * y y = 2 * x_old if (x-4)**2>(y-7)**2: return "C" else: return "D" else: return "H" print(if_confusion(2, 8))
โ Do:
def if_confusion(x,y): if x>y and x>5 and y==0: return "A" if x>y and x>5: return "B" if x>y and x+y>0: return "E" if x>y and 2*x==y: return"F" if x>y: return "G" if x>y-2 and (y*y-4)**2>(2*x-7)**2: return "C" if x>y-2: return "D" return "H"
Principle 16: Use Metrics
James Grenning, co-author of the Agile Manifesto, estimates that the reduction of cyclomatic complexity can improve code understanding by 35%. Track complexity with metrics tools and use the feedback to guide your refactoring efforts.
Principle 17: Boy Scout Rule and Refactoring

Google observed a 25% decrease in system defects when following the Boy Scout Rule. Clean up a little bit of code every time you visit it; even if it’s just reordering methods, your future self and your team will thank you.
Conclusion
Adopting clean coding principles takes discipline but witnessing the fruit of your efforts in producing clear, concise, and maintainable code is incredibly rewarding.
Take these principles to heart, apply them in your programming routine, and watch as your codebase transforms into a well-oiled machine, ready to accommodate growth, collaboration, and innovation in stride.
Finally, don’t underestimate the power of simply copying your code in full or in parts in ChatGPT and asking it for feedback! Lift the quality of your code with the rising tide.
In the next chapter, youโll learn another principle of effective coding that goes beyond just writing clean code: premature optimization. Youโll be surprised about how much time and effort is wasted by programmers that havenโt yet figured out that premature optimization is the root of all evil!
๐ Stay tuned!