Clean Code & Code Quality
Clean Code & Code Quality
Clean code is code that clearly communicates intent to the next reader (who is often yourself six months later). It is not necessarily shorter or faster — it is readable, testable, and changeable. These properties matter because the cost of software is predominantly in maintenance, not initial development.
Naming
Names are the most important element of clean code. Good names eliminate the need for comments.
Variables: name the concept, not the type. userList is worse than activeSubscribers. d is unacceptable unless scope is 2 lines. data is almost always wrong — data of what?
Functions: verbs. createOrder(), sendWelcomeEmail(), validatePaymentMethod(). If you cannot name a function with one verb phrase, it probably does more than one thing.
Boolean flags: prefix with is/has/should/can. isAuthenticated, hasExpired, shouldRetry. authenticated could be a noun (a person) or an adjective — the prefix clarifies.
Classes: nouns representing concepts. UserRepository not UserDatabaseHandler. Classes named after processes (DataProcessor, InfoManager) are often SRP violations in disguise.
Avoid: abbreviations (acct, usr), encodings (iUserCount, strName — type information belongs in the type system), noise words (Manager, Handler, Processor, Info, Data, Object added to a name that already says everything).
Functions
A function should do one thing, do it well, and do it only. Robert Martin's heuristics:
Length: functions should rarely exceed 20 lines. Not a rule but a smell — longer functions likely do multiple things.
Arguments: 0-2 arguments is ideal. 3 is acceptable. More than 3: extract a parameter object. Flag arguments (a boolean that controls two behaviors) should be split into two functions.
Side effects: a function named checkPassword() should not also reset the session. Unexpected side effects are where bugs hide.
Levels of abstraction: a function should operate at one level of abstraction. Mixing high-level logic (process order) with low-level details (parse date string) in the same function makes both harder to understand.
Structure
Early returns: validate inputs and return early rather than wrapping everything in else blocks. Arrow anti-pattern: code with 5 levels of nesting from else-if chains. Early return removes nesting.
Guard clauses: if (!user) return null; if (!user.isActive) return null; // real logic here is cleaner than nested ifs.
Extract functions: when you feel the urge to write a comment, extract that block into a function with a descriptive name. // calculate compound interest → calculateCompoundInterest(principal, rate, years).
Horizontal and vertical formatting: related code should be close. Variable declarations near first use, not all at the top. Methods that call each other should be adjacent. Blank lines separate logical sections.
Code Smells
Long methods: doing too much, too complex.
Large classes (God objects): know too much, do too much. Break into focused classes.
Feature envy: a method uses another class's data more than its own. Move it to the class it envies.
Primitive obsession: using primitives (strings, ints) where domain objects belong. A phone number isn't a string — it has formatting, validation, country code. A Money type prevents accidentally adding USD and EUR.
Shotgun surgery: one change requires edits in many files. Symptom of incorrect code organization — related concepts are spread across modules.
Dead code: commented-out code, unreachable branches, unused functions. Delete it — version control remembers it.
Duplicate code (DRY): copy-paste is the most common source of bugs. Extract duplicated logic. But don't over-DRY: two similar-looking functions in different domains may diverge. Premature DRY creates wrong abstractions that are harder to fix than the original duplication.
Cognitive Complexity
The human working memory holds ~7 items. Deeply nested code, complex boolean expressions, and long chains of transformations exceed this limit, making reasoning error-prone.
Cognitive complexity measures this: each nesting level adds weight, each structural break (continue, break, recursion) adds weight. Code analysis tools (SonarQube, ESLint's complexity rule) measure this automatically.
Reducing cognitive complexity: extract nested logic into named functions, replace complex boolean expressions with named predicates (isEligibleForDiscount()), convert deeply nested loops to functional chains (map/filter/reduce).
The Boy Scout Rule and Technical Debt
Technical debt is the cost of rework caused by choosing a fast solution now over a correct one. Like financial debt, it accrues interest: the longer it persists, the harder it becomes to fix as more code depends on the poor design.
The Boy Scout Rule — leave code cleaner than you found it — prevents debt accumulation. Fix one variable name, extract one function, remove one dead branch every time you touch a file. Over months, the codebase improves continuously without requiring dedicated "cleanup sprints."
This doesn't mean refactoring without tests. Refactor in tested code with a clear behavioral contract; refactoring untested code is rewriting, which is a different risk.