Greetings 👋:👋:👋:,
This is the summary of 📖 Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin 📖. I am trying to write this summary to strengthen my understanding of this book and try to implement Feynman Technique.
I hope I can make the sentence as simple as easy to understand for non-native English speakers (like me), so feel free to contribute, comment, and pull requests if there are any grammatical mistake 😁
- Chapter 1 - Clean Code
- Chapter 2 - Naming
- Chapter 3 - Function
- Chapter 4 - Comments
- Chapter 5 - Code Formatting
- Chapter 6 - Object & Data Structures
- Chapter 7 - Error Handling
- Chapter 8 - Boundaries
Chapter 1 - Clean Code
Creating a clean code is important, it might help you to add more features in the future easily. Have you ever been slowed down by someone else's messy code? That's why writing a good code is important. So, what is clean code?
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)
Chapter 2 - Naming
Naming is easy yet hard to choose when you coding something, it might indicates that you are writing a good code or a bad code. There are several indicator to follow when naming a variable, function, or classes:
-
Use Intention-Revealing Name
❌ int d; ✅ int daySinceCreated; ✅ int dayLeft; ❌ function calc(int num1, int num2) { ... } ✅ function multiply(int num1, int num2) { ... }
-
Make Meaningful, Descriptive, and Non-ambigous Name
❌ string date1, date2; ✅ string startDate, endDate; ❌ float pft, profit ✅ float profitBeforeTax, profitAfterTax ❌ let productData, productInfo ✅ let productData, productDescription
-
Use Pronounceable Name (Don't use abbreviation/acronyms)
❌ let rltdJobPst; ✅ let relatedJobPost;
-
Use Nouns For Classes, Variables and Use Verb For Function
❌ let setCurrentJobPost; ✅ let currentJobPost; ❌ function jobpost() { ... } ✅ function getJobPost() { ... }
-
Pick a Single Word per Concept and Use It Widely Across The Entire App
❌ function findJobPostById { ... } ❌ function fetchJobPostDetail { ... } ❌ function getJobPosts { ... } ❌ function findCurrentJobPost { ... } ✅ function getJobPostById { ... } ✅ function getJobPostDetail { ... } ✅ function getJobPosts { ... } ✅ function getCurrentJobPost { ... }
-
Avoid Unnecessary Word
❌ JobPost[] jobPostArray ❌ JobPost[] jobPostList ❌ JobPost[] sevenJobPostList // We already know the variable will store list/array. So do this instead ✅ JobPost[] jobPosts
-
Avoid Meaningless Prefix/Suffix
❌ const projectInfo ❌ const jobPostData ✅ const project ✅ const jobPost
And, last but not least
-
Follow common naming convention
If you are coding with
Javascript
usecamelCase
convention. If you are coding withPython
usesnack_case
. If your are writehtml
usekebab-case
Chapter 3 - Function
These are some several rules, to make your function cleaner:
-
A function should be small and short
Make the function not so big so the it fits into your screen, so you can read all the function does without scrolling vertically or horizontally. This approach will make you grasp the purpose of the function in the first place from the first line into the last line.
It should be readable from top to bottom as a paraghraph
➖➖➖
-
A function should do just one thing
Function should only do one thing, They should do it well, and they should do it only
Function should be only do the
verb
that the function name suggest. If you can still extract a function from the function, so it is not small enoung and do more than one thing.❌ function order(int id, boolean clearCart, boolean flushOrderSession) {...} ✅ function order(int id) {...} ✅ function clearCart(int id) {...} ✅ function flushOrderSession(int id) {...}
➖➖➖
-
Use one level of abstraction
Make sure that your function has only one level of abstraction. From my understanding, your function shouldn't has a nested condition since reading a nested is will confused you. If you have a nested condition within your function, you better break the function into smaller part;
// ❌ Avoid a function which has nesterd condition createUser(user) { let shouldDoAMilitaryWork; let isSmoking; if(user.gender === GENDER.MAN) { if(user.age > 17 && user.age < 40) { shouldDoAMilitaryWork = true; } else { shouldDoAMilitaryWork = false; } if(user.isSmoking) { isSmoking = false; } else { if(user.isHealty) { ... } else { ... } } } else { ... } } // ✅ Do like this instead createUser(user) { const shouldDoAMilitaryWork = isShouldDoAMilitaryWork(user); const isSmoking = isUserSmoking(user) } isShouldDoAMilitaryWork(user) { ... } isUserSmoking(user) { ... }
➖➖➖
-
Avoid switch statement
//TODO: Need to read once more to fully understand this one Use polymorphism and bury the switch statement in an abstract factory.
➖➖➖
-
Use descriptive name
The name of the function should be clear, it suggest one thing. And from the name of the function the reader should have a clue what does the function is doing.
Usually the function is in
verb
form. Since the function is will doing something, but sometime the function name also could be in question format. It is okay to make a long function name, as long as the is no shorter way to describe what does the function is doing❌ function onHandleChange() {...} // Not describing what does the function do ✅ function setUserName(String name) {...} ✅ function isUserActive() {...}
➖➖➖
-
Function Argument
Some point you should have to consider when creating a function:
- Better to use no argument
- You can add one argument or two. But better to avoid three or more argument
- If you really need to pass three or more argument, consider to pass a map/object
- Avoid flag argument. Avoid a flag which contains boolean value
❌ function saveUser(name, address, age, gender) {...} ✅ function saveUser(user) {...} ❌ function saveUser(isFromEdit) {...} ✅ better to make seperate scenario that will invoke same function instead of avoid flag
➖➖➖
-
Have No Side Effect
Your function should has no side effect, it should only do what the name suggest. Don't do anything else
function isUserValid(username, password) { const isValid = // some operation to check user within databse if (isValid) { createSession(x); // ❌ this cause a side effect } return isValid; }
➖➖➖
-
Output Arguments
Avoid setting an output as argument to a function, it ambigous and hard to add changes in the future. Do something like this instead:
❌ activateJob(job) { ... } ✅ job.activate() { ... }
➖➖➖
-
Command Query Separation
Your function should do a command or a query. But not both
❌ boolean setRole() { ... } ✅ void setRole() { ... }
➖➖➖
-
Prefers Exception to Error Code
Prepare an exception block to catch an error, rather than to leave the error code as is. It is a best practice to catch error and handling it in certain way.
transformUserToStaffFormat () { {}.map() // ❌ Will caused error with no handler } ✅ transformUserToStaffFormat () { try { {}.map() } catch (error) { handleErrro(error) } }
➖➖➖
-
Don't Repeat Yourself
When you write a function, it should minimize or even remove repeated code or purpose since a function should represent one purpose. And also, please noted that when you are wiriting function you should take a notice that you shouldn't do repeated code
Chapter 4 - Comment
Generally, comments is bad. You should avoid comment in your code as possible. From Chapter 2 - Naming and Chapter 3 - Function you should already aware that your code should self explanatory, it should express the purpose. So avoid using comment in your code.
But there is some exception to white comment:
- TODO comment
- Legal comment
- The necessary comment in case the code couldn't speak it self (ex: regex pattern)
- Clarification and Amplification comment
Clarification and amplication comment example:
const mapResponseToForm = () => {
...
// This is the best way so far to avoid to prevent race condition. Don't remove
setTimeOut(() => {
setFormTypeBasedOnProjectCategory(response.project_category);
}, 1000)
}
// Avoid using this function unless you really need to kill entire app
const killProcess() {
...
}
In conclusion, Don't leave a comment in your code!!!
Chapter 5 - Code Formatting
Code formatting is important. It is too important to ignore, code formatting is about communication, and the communication is the professional developer's first order of business. It is even more important than getting it work since the requirements might be changed tommorow, but code realibility will be the core of your application in the future for maintainibility or future changes
Think like a well-written newspaper. You read it vertically, at the top you expect the overall view of the story telling you about, and then you will face the first paragraph which tells you the synopsis of the story. And as you continue down, you will more dig dive into the detail.
You should think as like writing a well-writter newspaper when you are coding. The code you are writing should self documentary to be able to tell the story to the readers.
Everyone might have their own style of formatting, but you might follow this rules (or convention, I think..):
- User vertical spacing, density, and distance to make it easy to read your file
- Group code by their functionality. Related function must be close. Related code must appear vertically dense. Additionally: me personally like to order function alphabetically to easily search the suggested function
- Avoid too long files. My personal preference is too keep one file around (or shorter than) 1000 lines
- Avoid too-wide code lines. Make sure the code are visible in a one screen, so you have a good picture of the file is telling you about
- Write high0level code first, followed lower level code. Keep the function which call another above the invoked function, so then you can read the flow top to bottom
- Follow proper identation across code files. Personally I prefer to follow language-formatting-convention
Chapter 6 - Object & Data Structures
When creating an application. We have to choose the right choice between Object and Data Structure. There is no the most good of all time between the two. but the usage may be different based on your case. Here what to remember when choosing Object or a Data Structure:
- Object exposes behaviour and hide data, it easy to add new kinds of data wihout changing behaviour. But, it hard to add new behaviour to existing
- While Data Structure exposes data and no significat behaviour. This make it easy to add new behaviours, but it hards to add new data structures to existing function
This law is telling about how or how should function invoked by others. The Law of Demeter says that a method f of a class C should only call the methods of there:
- C
- An object create by f
- An object passed as an argument to f
- An object held in an instance variable of C
Chapter 7 - Error Handling
Clean code is readable, but it must also be robutst. One way to write robust clean code if we see error handling as a seperate concern. These are some tips when dealing with error handling:
- Seperate your logic and your error handling. We should have a clear seperation between error handling and bussiness logic. Avoid using if statement to check if the code is contains error or not
- Provide Context. The error handling should tell the readers that receive the error the aduquate context. It should whats is going on, why the code is error, where the error happens
- Map unknown error. Wrap all errors and exceptions raised from external systems
- Don't Return or Pass Null. Avoid returning null
Chapter 8 - Boundaries
When you use third party code, you will be presented with many capabilities of the code, usually third party code makers target a wide audience. But as users, they only needed a few abilities that were just what they needed. In the book it is mentioned that, a good way to use third party code is to create an `Adapter` or `Boundary Class` to create an interface from the third party that we use to make the code cleaner. For example:When you installing axios
, and read the documentation. You may read these API related to HTTP request (or even more than these if you deep dive into the API):
// Instance methods
axios.request(config);
axios.get(url[, config]);
axios.delete(url[, config]);
axios.head(url[, config]);
axios.options(url[, config]);
axios.post(url[, data[, config]]);
axios.put(url[, data[, config]]);
axios.patch(url[, data[, config]]);
axios.getUri([config]);
Let say, you just need the get
method and the delete
method. Yes, of course you can do such a axio.get
within your app to do a HTTP Get Request. But, it is not clean, why?
Because we have no control over the 3rd party code, we have no control over whether the code will stay the same for a long time or not. The problem is, if the code changes in the near future and it changes the way we use the API. So, we have to change all the code that uses that code in many places. Hence, it is better to use it like this:
// ✅ request-adapter-service.js
import axios from 'axios';
export default class RequestAdapterService {
sendGetRequest(URL, params) {
return this.reqClient.get(URL, { params });
}
sendDeleteRequest(URL, requestBody) {
return this.reqClient.delete(URL, requestBody);
}
}
//some-feature.js
const service = new RequestAdapterService();
const response = service.sendGetRequest('http://fakeapi.com', { limit: 2});
with class RequestAdapterService
we limit the usage of axios
for only the things that we need. and also, we can control what we have to do with the sendGetRequest
API. And it's in one place, where (let say) axios
changes in the future. We just need to change the file request-adapter-service
➖➖➖
One of the benefits of using Boundaries
is that we can guess or prepare for development using something we don't know yet. We can create an interface that can bridge the real API (which we don't have yet) with the API we expect
➖➖➖
We've talked about 3rd party code above, we as developers may be familiar with using 3rd party code. sometimes, we spend a lot of time just studying documentation and trying things out. But there is some better way to understand the 3rd party code.Learning Test is a technique to learn 3rd party library. This way we ensure that the library we are going to use will match the expected output we want. And this test can be a reference to find out the library can still be in accordance with the development of the application that we make
➖➖➖
Clear at boundaries needs clear separationn and tests that define expectation. avoid letting too much of our code know about the third party particulars. Better to depend on something you can control than on something you don’t contro