The distinction between “code that reads like poetry” and “code that feels like a mess” is subtle, yet it defines the core competence of a developer. In Clean Code, Robert C. Martin (“Uncle Bob”) elaborates on the causes of bad code and how to maintain good coding habits. In fact, the book’s table of contents itself serves as a checklist for writing better code. If you want to write cleaner code but truly cannot spare the time, even a quick scan of the chapter titles will yield value. However, if you are pressed for time even for that, the central principle the book relentlessly champions is simple: DRY (Don’t Repeat Yourself) and maximize expressiveness. Here are the sections I found most insightful.
Why Do We Need Concise, Clear Code?
Most of us have faced the dilemma of having to rewrite code. The reasons are numerous: convoluted thinking, tight deadlines, or simply making hacks to get the project live on time. Code, like food, can rot. Today, you write bad code to meet a deadline; tomorrow, a confused mental state leads to even worse code. Eventually, you are left with a mountain of garbage. As long as the project survives, it will release countless demons to haunt you in the days ahead. Fed up, developers will rebel, demanding to tear everything down and start over. What they fail to realize, however, is that rewriting a project often doesn’t solve the original problems; it merely introduces new, unknown ones. Rather than jumping to a rewrite, it’s better to write concise, clean code from the outset.
About Functions
Good functions follow a clear pattern: They do one thing, they do it well, and they do it only. To achieve this, we need to separate the entire project into layers of abstraction. Consider a hypothetical function for cooking.
1 | def cook(ingredients): |
This single function is too long. It attempts to tackle the entire process—material selection, washing, chopping, blanching, and cooking—all at once. If even a single variable goes wrong during execution, the output might be incorrect. This function has poor maintainability. If you frequently have thoughts like [Trigger Warning]:
- I understand the concept, but I don’t know where to start writing.
- I swear I solved this problem in the code above; why is it reappearing now?
Since a function is so complex that you cannot pinpoint the error or even know where to begin, why not decompose the problem?
1 | def prepare_and_cook(ingredients): |
At the top-level cooking abstraction, I only care about the essential steps of preparing the meal. How each step is precisely executed is abstracted down to the next level. The advantage here is that the problem is refocused. Instead of tackling “how to cook” as a giant monolithic task, we can now complete each step independently, conquering them one by one.
Furthermore, comments do not help you debug. Look back at your past projects—did those gray or green comments really assist you while you were debugging? Good code speaks louder than detailed comments. For example:
1 | // Check to see if the person is able to purchase a product |
(Note: Corrected a typo in pruduct to product above for technical precision.)
Can you easily understand this? Consider a different approach:
1 | if(person.is_able_to_buy(product)){ |
As before, defer the implementation details to the next layer. The price of improved maintainability is merely refining problems into smaller functions. Functions should be focused and short. In fact, many corporate style guides mention function size. For instance, the Google Python Style Guide states:
If a function exceeds about 40 lines, think about whether it can be broken up without harming the structure of the program. ^1
Formatting and Scale
The author believes that establishing a set of style guidelines before work begins significantly increases team productivity. Beyond keeping functions short, there are many other conventions. For example, it’s often recommended that code lines do not exceed 80 characters. In fact, many contemporary IDEs can display a dashed line to visually remind you to keep lines within that limit.

About Error Handling
You should avoid writing multiple if...else... or switch statements specifically for exception handling. Most modern programming languages provide robust, dedicated exception handling mechanisms.
1 | # BAD PRACTICES (Error Codes) |
(Note: Corrected small logical errors and consistency in the pseudocode above to better illustrate the point.)
Instead of using various if statements, a more reasonable approach is to encapsulate these operations within exception handling blocks. This improves both the readability and maintainability of the code.
These are a few highlights from the book that deepened my understanding of programming. Ultimately, a developer’s coding proficiency is directly proportional to the amount and quality of code they read. For example, the day after I read about layering functions to decompose a problem, I was able to apply it to my own code. I noted in another (unpublished) notebook that I needed to implement a function that accepted a DataFrame and changed a specific column to 1 or 0 (binarization). At the time, my understanding of Python was not deep (still isn’t, ha!), and the resulting code was highly redundant:
1 | # Redundant approach: Changing price to only affordable (1) or unaffordable (0) |
Then I saw a function written by Hasan:
1 | # Idiomatic and concise approach using NumPy |
See? The same objective, but written by different minds, produces different results. This is the gap… In programming, it really is a matter of seeing more and writing more.
Summary
Excerpts from “Clean Code: A Handbook of Agile Software Craftsmanship” by Robert C. Martin.
What Is Clean Code?
- Logic should be straightforward, making it hard for bugs to hide.
- Minimize dependencies to facilitate easier maintenance.
- Perfect error-handling code according to a clear layering strategy.
- Optimize performance to avoid tempting others into reckless optimizations.
- Clean code does one thing.
- Simple, direct, and readable.
- Covered by unit and acceptance tests.
- Meaningful names.
- Code should express its meaning literally.
- Minimize entities: classes, methods, and functions.
- No duplication (DRY).
Meaningful Names:
- Use Intention-Revealing Names: Names should convey meaning and purpose without requiring comments.
- Avoid Misleading:
(1) Do not use system-reserved or language-specific keywords as variable names.
(2) Beware of names with very subtle differences; they are easy to mis-select with autocomplete.
(3) Avoid using the letterslandOas variable names, as they can be easily confused with1and0. - Make Meaningful Distinctions: Do not differentiate names using numbers or arbitrary filler words (e.g.,
product1,productData). - Use Pronounceable Names: Names should be easy to say.
- Use Searchable Names: Avoid single-letter names or unnamed numeric constants that are difficult to locate globally.
- Avoid Encodings:
(1) Do not encode type information in names; modern languages have robust type checking.
(2) Do not use prefixes for member variables; modern IDEs highlight members.
(3) Prefer coding to implementations, not to interfaces. (Original context usually refers to naming conventions likeIShapevsShape). - Avoid Mental Mapping: Choose names that other people will naturally understand, not something specific only to your personal mapping.
- Class Names: Should be nouns or noun phrases (e.g.,
Customer,WikiPage), not verbs. - Method Names: Should be verbs or verb phrases (e.g.,
postPayment,deletePage). - Avoid Colloquialisms.
- Use One Word per Concept: Stick to one term for each abstract concept (e.g., don’t mix
fetch,retrieve, andgetfor the same operation). - Avoid Puns: Do not use the same word for two different purposes (e.g.,
addfor appending versus mathematical addition). - Use Solution Domain Names: Use CS terms, algorithm names, or mathematical terms when appropriate.
- Use Problem Domain Names: Fall back to terminology from the problem domain if no solution domain terms fit.
- Add Meaningful Context: For example, use an
Addressclass to encapsulate related context like street, city, state, and zip code. - Don’t Add Gratuitous Context: Keep names as short as they can be while still conveying their meaning clearly.
Functions:
- Small:
(1) Functions should rarely exceed 20 lines.
(2) Blocks withinif,else, andwhilestatements should be only one line long and ideally contain a single function call. - Do One Thing: If a function can be meaningfully broken down further, it is doing more than one thing.
- One Level of Abstraction per Function: Follow a top-down narrative, moving down one level of abstraction with each step.
- Switch Statements: Use them sparingly, primarily to create polymorphic objects, and hide them behind an abstract factory pattern.
- Use Descriptive Names: A name should clearly describe what the function does.
- Minimize Function Arguments: Ideally, functions should have zero, one, or two arguments (niladic, monadic, dyadic). Avoid three or more.
(1) Return values are preferable to output arguments.
(2) Never use flag arguments (passing a boolean to change behavior); split the function instead.
(3) If multiple arguments are necessary, consider encapsulating them into a class. - Have No Side Effects: Functions should not do anything other than what their name implies.
- Prefer Exceptions to Returning Error Codes: This separates the error-handling logic from the happy path.
(1) Extracttry/catchblocks from normal processing flows. - Eliminate Duplicate Code (DRY).
- Use Early Returns, Breaks, and Continues Moderately: In small functions, using
return,continue, orbreakis acceptable, but avoidgoto. - First, Write It to Work; Then, Refactor It to Be Clean.
Comments:
- Minimize Comments; Let Code Speak: Comments should be a last resort.
(1) Code changes frequently, but comments often do not.
(2) The code itself is the only truly accurate source of information.
(3) If you need a comment, try refactoring the code into a well-named function instead. - Good Comments:
(1) Legal copyright/license information.
(2) Clarifying the developer’s intent.
(3) Warning other programmers about potential consequences.
(4)// TODOmarkers (but delete them regularly).
(3) Javadoc/documentation for public APIs. - Bad Comments:
(1) Mumbling: Comments that only the author can understand.
(2) Redundant comments that are less expressive than the code itself.
(3) Mandated Javadoc for every single variable or function.
(4) Journaling: Keeping a modification log in comments.
(5) Don’t use a comment when you can use a well-named function or variable.
(6) Do not use comments to indicate ownership (use source control instead).
(7) Never leave commented-out code; rely on your version control system for history.
Formatting:
- Vertical Formatting:
(1) Variable declarations should appear close to where they are used; local variables should be at the top of a function.
(2) Instance variables should be declared at the top of the class.
(3) Related concepts/functions should be kept vertically close.
(4) Maintain caller/callee ordering to preserve the top-down flow. - Horizontal Formatting:
(1) Keep lines short, ideally between 100-120 characters.
(2) Use spaces to separate things that are not strongly related (e.g., around operators, after commas).
(3) Horizontal alignment for declarations is unnecessary.
(4) Indentation: Use consistent indentation. Even emptywhileloops should be formatted clearly or have the braces explicitly included to avoid missing the trailing semicolon.
Tests:
- Three Laws of TDD (Test-Driven Development):
(1) You may not write production code until you have written a failing unit test.
(2) You may not write more of a unit test than is sufficient to fail; not compiling is failing.
(3) You may not write more production code than is sufficient to pass the currently failing test. - Keep Tests Clean: Test code is as important as production code and requires the same standards of cleanliness.
- One Assertion per Test: Ideally, keep assertions to a minimum (or one logical concept per test).
Concurrency:
- Concurrency Defense Principles:
(1) Single Responsibility Principle: Concurrency management is complex enough to be its own responsibility; keep it separate.
(2) Limit Data Scope: Strictly control access to shared data.
(3) Use Copies of Data: Favor working with independent copies of data to avoid sharing.
(4) Threads Should Be as Independent as Possible. - Understand Common Execution Models: Producer-Consumer, Readers-Writers, Dining Philosophers.
- Beware of Dependencies Between Synchronized Methods.
- Keep Synchronized Sections Small.
- Think About Shutdown Early: Plan how threads will shut down cleanly, paying attention to potential inter-thread dependencies.
- Test Threaded Code:
(1) Do not ignore occasional, non-reproducible failures (pseudo-failures). They are real issues.
(2) Ensure non-threaded code works first.
(3) Run with more threads than processors.
(4) Run tests on different platforms.
(5) Intentionally instrument code withwait(),sleep(), etc., to try and force failures.