1. 程式人生 > >Clean Code by Uncle Bob book review (first chapters)

Clean Code by Uncle Bob book review (first chapters)

Clean Code by Uncle Bob highlights (chapters 1–3)

Probably most people working as professional software developers for some time have heard of the book “Clean Code” by Robert C. Martin. It is one of those books usually regarded as must read for software engineers.

This is not meant to be a substitute to reading the book, I think reading it is worth it and most people will learn something: a style tip. technique, a golden nugget to improve their style and become better coders.

I wrote this as a sort of reminder, it’s good to review this concepts after a while, it serves us well to learn them and evaluate if they are worth it.

Also, plenty of companies recommend this book as a “good to know” for coding interviews, and its collection of principles may seem basic for someone with many years of experience. But as the introduction of the book states:

“When developing software at a professional grade meant to be read by others and maintained for hopefully a long time, design details and small things matter… more that we may think, always remember, what is evident to us may not be evident to others.”

Also in the foreword James O.Coplien states:

“Back in my days working in the Bell Labs Software Production Research organization, we had some back-of-the-envelope findings that suggested that consistent indentation style was one of the most statistically significant indicators of low bug density.”

This shows us that when coding, simple investments pay huge dividends in the future.

The examples I wrote alongside some principles are written in JavaScript, in “Clean code” the sample code is written in Java.

About the format of the book

The book is divided into three parts:

  • The first part describes the theory of writing clean code, the principles, patterns and practices.
  • The second part are case studies, each case study is an exercise in cleaning up some code.
  • The third part is the roundup of the code smells and heuristics gathered in the second part of the book.

In this post I’ll focus on the first part, specially the first three chapters, they are about choosing meaningful names, writing good functions and I’ll give a brief gist about the third chapter on writing good comments.

Chapter 1 — Meaningful Names

Choosing meaningful names requires both descriptive skills and a shared cultural background. Do not be fearful of changing names as long as they change for the better.

Good naming checklist:

  • Use intention revealing names

The name of a variable, function or class should answer three questions, It must tell you why it exists, what it does, and how it is used.

const m = 360000; // Gives no information
const milisecondsInHour = 360000; // More understandable`

Try not to rely too much on the context of the code to choose its naming, doing such thing will make variables less explicit.

  • Avoid Disinformation

Avoid long names that vary slightly, and avoid words that could give a false hint to the next programmer.

// Too much of a similar shape
const XYZControllerForEfficientHandlingOfStrings = …
const XYZControllerForEfficientStorageOfStrings = …
// Actual data struct is not a list, name misinforms
const clientList = new Set(retrievedClients);
  • Make meaningful distinctions

To satisfy a compiler or and interpreter sometimes we vary names in slight and subtle ways to avoid two names clashing in the same scope. Try that names sharing a scope are distinguishable from one another.

  • Use pronounceable names

If you can’t pronounce it, then you can’t discuss it without sounding silly.

const genymdhms = Date.now(); // Quite unpronounceable
const generationTimestamp = Date.now(); // Way better
  • Use searchable names

It is not easy to find single letter variables, try using this variables only as local variables in short methods.

  • Avoid encodings

Don’t encode the type or scope of a variable in its name, avoid Hungarian notation.

  • Interfaces and implementations

Some special cases where encoding brings value are implementations of design patters (i.e.: factory functions) and interfaces.

  • Class names

A class name should be a noun, not a verb. Think “Client”, “Address”, “Account” instead of “Manager”, “Processor” and “Mapper”.

  • Method names

Method names should preferably have verb of verb phrase names, for example “fetchUsers”.

  • Pick one word per concept

If you have verbs like “fetch”, “retrieve” and “get”, or “remove”, “delete” and “eliminate” as equivalent methods in different classes you should chose one convention and stick to it.

  • Don’t pun and don’t be clever

Avoid jokes, it’s not worth it, jokes vary widely with cultural context and not everybody shared your sense of humor. Avoid using the same word for two different purposes.

Choose clarity over entertainment value

  • Use solution domain names

Remember that the people reading your code will be programmers. Don’t shy away from CS terms like data structures, known algorithms and design patterns.

  • Use problem domain names

When all else fails, domain experts will be able to make sense of the naming.

Chapter 2 — Functions

Functions are the first line of organization of any program, it pays off to write them with some style tips in mind. After the first draft of a function, give yourself some time to think about these concepts and refactor them until they follow this rules.

  • Keep them SMALL

Keep functions as short as possible, functions should do one thing and do it well. This is not only key to readability and it goes beyond the “cleanliness” aspect, short functions are easier to test ?

  • Don’t repeat yourself

Abstract reusable code in short functions, eliminate duplication from your code.

  • Blocks and indenting

Blocks inside “if”, “while” and “for” statements should only be one of a few lines long, more than that implies that that block should be a function call with a nicely descriptive name.

Also functions should not be long enough to hold nested structures, the indentation level of functions must be one or two at most.

  • Do one thing

Functions must do only one thing and they should do it well.

  • One level of abstraction per function

To ensure that our functions are doing one thing well we must make sure that our statements are in the same level of abstraction.

// Check out how the username validation is extremely low level in // comparison with the registration function.
const registerUser = (username, data) => {
  if(typeof username !== ‘string’){
    throw new Error(‘username must be a string’);
  }
  if( /^([A-Z0–9]){12}$/.test(username)){
    throw new Error(‘Invalid username format’);
  }
  userWriter.register(username, data);
};
// Much more coherent
const registerUser = (username, data) => {
  inputValidator.validateUsername(username);
  userWriter.register(username, data);
};
  • Reading code from top to bottom

We want every function to be followed by those at the next level of abstraction, from higher to lower. This is called The step-down rule.

  • Avoid switch statements

When you can, avoid switch statements, they tend to grow out of control. In times when we can’t avoid switch statements we should bury them inside a low-level class and hide them behind that wall of abstraction.

  • Limit function arguments

Always favor using less arguments, functions using a large amount of arguments are hard to reason about and to test.

  • Avoid flag arguments

Flag arguments imply that a function does more than one thing. This is a good opportunity to split the function in two.

// This function can be split in two and avoid flag arguments.
const saveUser = (userData, isAdmin) => {
  inputValidator.validateUserData(userData);
  if(isAdmin){
    userWriter.saveAdmin(userData);
  }else{
    userWriter.saveClient(userData);
  }
}
// Two simple functions is a preferred way to avoid flag arguments.
const saveAdmin = (userData) => {
  inputValidator.validateUserData(userData);
  userWriter.saveAdmin(userData);
}
const saveClient = (userData) => {
  inputValidator.validateUserData(userData);
  userWriter.saveClient(userData);
}
  • Argument objects

When a function needs a lot of arguments consider wrapping them into a class of their own.

const saveDog = (name, breed, age) => {
  // Do something…
};
const saveDog = (dog) => {
  // Do something…
};
  • Have no side effects

With the popularity rise of functional programming, and its preference for pure functions this sounds familiar. Functions should not have side effects other than the ones specified in their intent. This results in unexpected changes.

class UserSession{
  constructor(Session){
    this._session = Session;
  }
  // This function has a side effect, it initializes a session.
  validateUserExistance(userName){
    InputValidator.validateUserName(userName);
    this._session.Init();
    return userReader.getUser(userName);
  }
}
  • Avoid output arguments

In general output arguments should be avoided, If a function must change the state of something, have it change the state of its owning object.

  • Command Query Separation

Your functions should either do something or answer something, but not both, if this happens consider dividing it into smaller reusable functions.

  • Prefer exceptions to returning error codes

Returning error codes violates the command query separation principle, you are informing when something went wrong and doing the action itself in the same function.

If you make your functions throw exceptions they fail explicitly and your caller must not deal with the error logic right away.

  • Extract try/catch blocks

Try to extract try/catch blocks into functions of their own, the responsibility of this functions are mostly error handling, inner functions just take care of normal processing and throw corresponding errors.

Error handling is one thing, functions that do error handling should not do anything else.

Chapter 3 — Comments

Comments are a necessary evil, they compensate for our failure to express ourselves in the code.

Try to prefer expressive variables and functions to comments.

Don’t leave commented out code or redundant or misleading comments.

Conclusion

That’s all folks, if you find this useful I’ll keep on writing about the concepts presented in the remaining parts of the book, they are plenty.

I highly recommend purchasing the book, here is the Amazon link to it: