IT Tips & Insights: Code is clean if it can be understood easily – by everyone on the team. If it can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility, and maintainability.
By Ignacio De Luca, Softensity Software Engineer
The responsibility of clean code is 100% the programmer’s. Their job is to defend it and refuse to make incorrect code (would a doctor operate with dirty hands?). Clean code is the only way to move forward.
“It’s not enough to write the code well. The code has to be kept clean over time … Leave the campground cleaner than you found it (Boy Scout Rule).”
– Robert C. Martin
Clean code is characterized by being elegant, efficient, readable, minimal, doing only one thing well in a single way, and having unit tests. Like a broken window, wrong code is the beginning of disaster. We read 10:1 more code than we write, and we won’t be able to write code if the surrounding code is unreadable.
General rules
- Follow standard conventions.
- Keep it simple, stupid. Simpler is always better. Reduce complexity as much as possible.
- Boy scout rule: Leave the campground cleaner than you found it.
- Always find the root cause. Always look for the root cause of a problem.
- Follow the Principle of Least Surprise.
- Don’t repeat yourself (DRY).
- Do not override safeties.
Design rules
- Keep configurable data (e.g.: constants) at high levels. They should be easy to change.
- Prefer polymorphism to if/else or switch/case.
- Separate multi-threading code.
- Prevent over-configurability.
- Use dependency injection.
- Follow the Law of Demeter: A class should know only its direct dependencies.
Understandability tips
- Be consistent. If you do something a certain way, do all similar things in the same way.
- Use explanatory variables.
- Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
- Prefer dedicated value objects to a primitive type.
- Avoid logical dependency. Don’t write methods that work correctly depending on something else in the same class.
- Avoid negative conditionals.
Names rules
- Choose descriptive and unambiguous names.
- Make meaningful distinctions.
- Use pronounceable names.
- Use searchable names.
- Replace magic numbers with named constants.
- Avoid encodings. Don’t append prefixes or type information.
Functions rules
- Small.
- Do one thing and they should do it well.
- Use descriptive names.
- Prefer fewer arguments. No more than three, if possible.
- Have no side effects.
- Don’t use flag arguments. Split method into several independent methods that can be called from the client without the flag.
Comments rules
- Always try to explain yourself in code. If it’s not possible, take your time to write a good comment.
- Don’t be redundant (e.g.: i++; // increment i).
- Don’t add obvious noise.
- Don’t use closing brace comments (e.g.: } // end of function).
- Don’t comment out code. Just remove.
- Use as an explanation of intent.
- Use as clarification of code.
- Use as a warning of consequences.
Source code structure
- Separate concepts vertically.
- Related code should appear vertically dense.
- Declare variables close to their usage.
- Dependent functions should be closed.
- Similar functions should be closed.
- Place functions in the downward direction.
- Keep lines short.
- Don’t use horizontal alignment.
- Use white space to associate related things and disassociate weakly related.
- Don’t break indentation.
Objects and data structures
- Hide internal structure.
- Prefer data structures.
- Avoid hybrid structures (half object and half data).
- Should be small.
- Do one thing.
- A small number of instance variables. If your class has too many instance variables, then it is probably doing more than one thing.
- Base class should know nothing about their derivatives.
- Better to have many functions than to pass some code into a function to select a behavior.
- Prefer non-static methods to static methods.
Tests
- One assert per test.
- Fast.
- Independent.
- Repeatable.
- Self-validating.
- Timely.
- Readable.
- Easy to run.
- Use a coverage tool.
Code smells
- Rigidity. The software is difficult to change. A small change causes a cascade of subsequent changes.
- Fragility. The software breaks in many places due to a single change.
- Immobility. You cannot reuse parts of the code in other projects because of involved risks and high effort.
- Needless Complexity.
- Needless Repetition.
- Opacity. The code is hard to understand.
Error handling
- Don’t mix error handling and code.
- Use Exceptions instead of returning error codes.
- Don’t return null, don’t pass null either.
- Throw exceptions with context.
“Clean code is code that is written by someone who cares”
– Michael Feathers
Guidelines
Clean Code…
✓ is elegant and efficient
✓ is simple and direct
✓ has meaningful names and is self-documenting
✓ is pleasing to read, and reads like well-written prose
✓ does one thing well
✓ does not tempt the developer to write bad code – on the flip side, when others change bad code, they tend to make it worse
✓ never hides the designer’s intent but is rather full of crisp abstractions and straightforward lines of control
✓ is when each method you read turns out to be pretty much what you expected (Least Astonishment Principle)
✓ can be read, and enhanced by a developer other than its original author
✓ has unit and acceptance tests
✓ is minimal (KISS and YAGNI)
✓ does not repeat itself (DRY Principle)
Meaningful Naming for Variables & Classes
Use intention-revealing names
X int d;
✓ int elapsedTimeInDays;
✓ int daysSinceCreatedDate;
✓ int daysSinceLastModifiedDate;
X function calc (int num1, int num2) {return num1*num2}
✓ function multiply (int num1, int num2) {return num1*num2}
Make meaningful distinctions
Avoid ordering/number series naming
X string date1, date2;
✓ string startDate, endDate;
Avoid mispelling.
X float pft, profit
✓ float profitBeforeTax, profitAfterTax
Avoid synonyms / closely-related terms
X let productData, productInfo
✓ let productData, productDescription
Choose descriptive and unambiguous names
X string sd, ed;
✓ string startDate, endDate;
Use pronounceable names
X float chqRetVal
✓ float chequeReturnsValue
Use searchable names
Replace magic numbers with named constants
X 7
✓ int MAX_CLASSES_PER_STUDENT = 7
Use comments to reveal additional information such as complex business logic, behaviors, assumptions, future suggestions, etc.
X string requestId;
✓ string requestId; // must be unique
Use nouns for classes, packages, and variables
X class HandleAccounts {…}
✓ class AccountsHandler {…}
Use verbs/verb phrases for functions
X function accounts() {…}
✓ function getAccounts() {…}
Pick a single word per concept and use it consistently throughout the source code.
X function findUserByID(string id) {…}
X function getRoleByKey(string key) {…}
X function fetchAllUsers() {…}
X function findRoles() {…}
✓ function getUserById(string id) {…}
✓ function getRoleById(string id) {…}
✓ function getAllUsers() {…}
✓ function getAllRoles() {…}
Prioritize using domain-related terms
X transactionsStorage
✓ transactionsCache
Avoid dis-information
Avoid using a word with an accepted meaning for something else.
X User[] activeUsersList;
✓ User[] activeUsers;
Avoid using the variable name’s implementation details (such as data structure, container type, length) in the variable name.
X User[] activeUsersArray;
✓ User[] activeUsers;
X User[] fiveAdminUsers;
✓ User[] adminUsers;
Avoid unpopular acronyms and other names that don’t make sense.
X float topm, topw;
✓ float turnoverPerMonth, turnoverPerWeek;
Avoid using comments for introducing variables/functions.
X int d; // elapsed time in days
✓ int elapsedTimeInDays;
Avoid using confusing letters for names (e.g. l (simple L), I (capital i), O (capital O)).
X int l, O;
✓ int limit, option;
Don’t encode additional details in names.
Hungarian notation to encode data type is bad. Instead, use types.
X var strFirstName;
✓ string firstName;
X var nUsers;
✓ int usersCount;
Avoid meaningless prefixes or suffixes.
X var orderInfo;
✓ var order;
X var userData;
✓ var user;
Related classes and variables should follow a common pattern.
X UserDataService, UserRecordsDAO, UserInfoExporter
✓ UserService, UserDAO, UserExporter
Follow a common case convention.
E.g.
Routes – URLs: lower case with hyphens (kebab-case)
> hello-world-in-the-snow
Routes – Names: lower case with underscores (snake_case)
> hello-world-in-the-snow
Models and Controllers: begin with an Uppercase letter (CamelCase)
> HelloWorldInTheSnow
> SnowController
Method: begin with a lower case letter (camelCase)
> getHelloWorldWithSnow()
HTML Attributes. lowercase with hiphens (kebab-case)
> id=”hello-world”
> class=”snow”
> name=”hello-world-with-snow
Migrations. lowercase with an underscore (snake_case)
> first_name
> last_name
> the_place_where_everything_started
Functions
- A function should be readable from top to bottom, as a paragraph.
- A set of functions should be readable from top to bottom, as a set of paragraphs.
- Keep functions small — short (Ideal: 4 lines, maximum: 60 lines, the whole function must fit into the screen, so that it’s easy to read without vertical scrolling) and not too wide (Up to 70 to 120 characters, it’s easy to read the whole line without horizontal scrolling).
- Less number of arguments (≤3) is better. Make argument lists/objects/object arrays to pass large data chunks into functions.
- A function should remain at a constant level of abstraction. It shouldn’t deal with both low level and high-level stuff at the same time.
- Avoid duplications (DRY — Don’t Repeat Yourself).
- A function should do only one thing (Keep it atomic). Avoid causing side effects (Do what its name suggests and nothing else).
- Error handling is one thing. Keep functions that do only that. Start them with try and end with catch and/or finally.
- Use try/ catch instead of conditions if possible (Asking for forgiveness is easier than requesting permission).
- At lower levels, throw exceptions instead of returning error codes.
- Avoid nested control structures. Replace such scenarios with functions or alternative strategies.
- Avoid switch statements (Hint: use polymorphism and bury the switch statement in an abstract factory).
- It’s better to have many functions than to pass some code into a function to select a behavior.
Avoid using boolean variables as function arguments. Using booleans with other arguments usually means that the function does more than one thing. Always split such functions into smaller functions.
X function getUsers(boolean status) {…}
✓ function getActiveUsers() {…}
✓ function getInactiveUsers() {…}
X function removeOrders(int id, boolean cleanCache, boolean updateLog) {…}
✓ function removeOrders(int id) {…}
✓ function cleanOrdersCache(int id) {…}
✓ function updateOrdersLog(int id) {…}
Avoid output arguments
(Hint: If returning something is not enough, then your function is probably doing more than one thing).
Pass by reference for a modification is bad.
X activate(user);
✓ user.activate();
A function can be a command or a query, but not both.
A setter is a command function. It should not return any value.
X function setRole(…): boolean {…}
✓ function setRole(…) {…}
A boolean function must answer yes/no.
✓ function isActiveUser(…): boolean {…}
✓ function usersAreValid(…): boolean {…}
Keep boolean functions in a positive tone.
X function isInactive(): boolean {…}
✓ function isActive(): boolean {…}
Code Organization & Formatting
- Group code by their functionality. Related functions must be close. Related code must appear vertically dense.
- Declare temporary variables close to their usage.
- Adapt company/team-wide code conventions. Agree on a common standard for code formatting. Either use an external tool or IDE options for auto-formatting.
- Avoid too long files. 1000–2000 lines are okay. Shorter, the better.
- Avoid too-wide code lines. Make sure they fit into the screen (Up to 70 to 120 characters). If they don’t fit, try to split them into multiple lines.
- Write high-level code first in a file and keep lower-level implementations towards the end of the file. (Good files are like newspaper articles, with a heading, the important stuff first, and details later.)
- Make sure the code is ordered as of the calling sequence. The caller should be before the callee.
- Follow proper indentation across code files. Use an external tool or IDE’s support.
Objects and Data Structures
- A data structure must expose data while having no behavior.
- An object must hide data while exposing the behaviors (via methods).
- Avoid accessing unknown data via method chaining. Only access the immediate methods (methods of own class, of objects, just created), immediate parameters (of instance variables or objects just created), but not further methods/parameters through chaining them (a.k.a. Law of Demeter / Principle of Least Knowledge).
- Functions and data structures can have some coupling by nature. It may be easier to add new functions than to change data structures (adding new data structures can cause breaking changes in functions).
- Functions and classes can have some coupling by nature. It may be easier to add new classes than to change functions (adding new functions can cause breaking changes in classes).
Error Handling
- At lower levels, throw exceptions instead of returning error codes. Keep error codes for communication between different layers, interfaces, or systems.
- Errors and exceptions must provide adequate context (such as intent, failure stage, error type, failed values, etc.).
- Map foreign errors to adhere to common standards. Wrap all errors and exceptions raised from external systems, third-party libraries, and APIs. Use a generic error type for unknown/unhandled cases.
- Reduce the reliance on return type and null checks (since they strongly link failure path information to the main flow). Instead of returning null/false, return a proper error object or throw an exception. Passing null to a function / next stage is also bad, avoid such practices.
Boundaries
- Map foreign behaviors into wrapper classes when integrating external systems and third-party code. Expose only limited capability for local use.
- Use the Adapter pattern to handle things that don’t exist (e.g. external systems not yet implemented / integrated). Make adapters consume the agreed API via proper use of interfaces. When a foreign API changes, only the adapter must change, without any major impact on local code.
Classes & Interfaces
- Inside a class/interface, arrange code in order: static variables in order of visibility (start from public) → instance variables in order of visibility (start from public) → constructors → methods grouped by functionality → getters, setters, equals, variable conversions, utility & helper methods, etc.
- Encapsulate utility methods. Make them private (or protected if they are exposed to tests).
- Make classes small. Shorter, the better (Ideal: 100 lines, Maximum: 1000 lines).
- Classes with exactly one responsibility and one reason to change are easy to manage. a.k.a. The Single Responsibility Principle.
- The class name must show its responsibility. Class names with Processor, Manager, Super often hints at an unfortunate aggregation of responsibilities.
- Multiple small classes are preferred over very few large classes. During change requests, such code reduces the affected code surface due to high separation of concerns.
- Organize code based on abstractions, not concrete implementation details. Build abstract agreements to facilitate interactions between code.
- Maintain high cohesion. In general, classes have a fewer number of instance variables and they are co-dependant with methods as a whole. If that cohesion is getting low, break the class apart.
- Too much inheritance is bad. Try to use composition more often.
Concurrency
- Keep concurrency-related code separate from other code.
- Severely limit access to any data that may be shared. Unless there are performance concerns, using copies of data is more preferred than sharing.
- Try to maintain data as independent subsets that can be processed by separate threads.
- Avoid using more than one Synchronized method on a shared object.
- Keep synchronized sections as small as possible.
- Think about shut-down early and get it working early.
Comments
- Express yourself in the code, but not in the comments. Keep comments for delivering any extra information. If the code is readable, the need for comments is very low.
- Warn the consequences of potential scenarios/actions. If the code is too brittle, instead of commenting, fix the code.
- Avoid noise and weak comments (negativity, complaints, humor, misleading opinions, redundant statements, changelog, too much information, irrelevant facts, comments needing additional explanations).
- Avoid commenting out code chunks. If the code is bad, rewrite it. Use version control systems for handling code that is expired, deprecating, or experimental. (Pro tip: If you comment out code for a very short term, consider using a TODO comment as a reminder to clean things up later).
Application Logging
- Follow a common logging practice across code.
- Use correct log levels and provide adequate context in log messages (e.g. timestamp, log level, code location, state information, parameter values, error descriptions, system banners).
- Do not log sensitive information (e.g. personally identifiable information, financial values, business data, auth info).
Clean code is not written following a set of rules. You do not become a software professional just by learning a list of what you do and what you’ve done. Professionalism and craftsmanship come from values and discipline in lists of what you should and should not do when creating a code.
At Softensity we promote clean code because the implications are versatile:
- Lowered maintenance costs
- Better security
- Better employee satisfaction
- Higher predictability in software development costs and releases
- Better software design that enables flexibility to deployments
- Better test automation
- Higher test coverage
- And fewer customer-facing quality problems
Related Resources
- Presented as cheat sheet by CosteMaxime.
- The essence of “Clean Code” [PDF] – a different summary
- How to write clean code? Lessons learned from “The Clean Code” [article]
- A summary of the fundamental principles of writing great code [article]
- Clean Code – “Uncle” Bob [videos]
Recommended Books
- Clean Code: A Handbook of Agile Software Craftsmanship [2007] by Robert C. Martin (“Uncle Bob”) — This book explains coding best practices to improve the readability of code and maintain quality with TDD.
- Refactoring: Improving the Design of Existing Code (2nd Edition) [2018] by Martin Fowler — explains coding best practices to improve existing code for better understandability and maintainability.
- The Pragmatic Programmer (2nd Edition): Your Journey to Mastery, 20th Anniversary Edition [2019] by Andrew Hunt, David Thomas — This book provides practical insights and advice on creating better software and rediscovering the joy of coding.
Hi, I’m Ignacio De Luca, a Software Engineer with more than 11 years of development experience. I’m strongly focused on .NET Technologies and particularly interested in team management. I am a Backend Developer in the process of becoming a Fullstack, with trends in both web development (mvc, asp.net, .net core) and mobile development (xamarin and xamarin forms). I consider myself a person who achieves what is proposed and who truly believes that success is the sum of everyday efforts.