This is a summary of the famous book "Clean Code-A Handbook of Agile Software Craftsmanship" by Robert C. Martin.
- The name of the variable, function, or class should tell you why it exists, what it does, and how it is used.
- If a name requires a comment, then the name does not reveal its intent.
- Avoid leaving false clues that obscure the meaning of code. For example, Do not refer to a grouping of accounts as an
accountList
unless it's actually aList
. It may lead false conclusions. SoaccountGroup
orbunchOfAccount
would be better choice. - Beware of using names which vary in small ways. Example:
ControllerFOrEfficientHandlingOfStrings
ControllerForEfficientStorageOfStrings
- Number-series namings(a1,a2,.. aN) are noninformative.
- The word
variable
should never appear in a variable name. The wordtable
should never appear in a table name.
- Don't use
genymdhms
(generation date,year,month,day,hour,min,sec) but use pronounceable names likegenerationTimestamp
- Single-letter names and numeric constants are difficult to locate across a body of text. Use searchable names like
WORK_DAYS_PER_WEEK = 5
instead ofi = 5
- If you must do encoding then encode implementation. Example: Don't encode interfaces like
IShapeFactory
. Keep interface as it isShapeFactory
and encode implementationShapeFactoryImp
.
- Don't use variable names that has speacial meaning to you. Spell out the whole word.
- Clarity is king!
- Classes and objects should have noun or noun phrase name like
Customer
,WikiPage
,Account
. - Avoid words like
Manager
,Processor
,Data
, orInfo
n the name of a class. - A class name should not be a verb.
- Methods should have verb or verb phrase names like
postPayment
,deletePage
, orsave
- When constructors are overloaded, use static factory methods with names that describe the argument:
Complex fulcrumPoint = Complex.FromRealNumber(23.0);
is better thanComplex fulcrumPoint = new Complex(23.0);
Don't use funny names or culture-specific names. Don't use whack()
to mean kill()
.
- Pick one word for one abstract concept and stick with it. It's confusing to have
fetch
,retrieve
, andget
as equivalent methods of different classes.
- Avoid using the same word for two purposes. Let's say we have
add
methods in different classes that recreate a new value by adding two existing values. Now if we are writing a new class that has a method that puts its single parameter into collection. Now, to call the new methodadd
would be pun. Call that methodinsert
orappend
instead.
- If the people who will read your code are programmers then go ahead and use CS terms, algorithm names, pattern names, math terms, etc.
- The name
AccountVisitor
means a great deal to a programmer who is familiar with the Visitor Patter.
- The code that has more to do with problem doamin concepts should have names drawn from the problem domain.
- You need to place names in context for your reader by enclosing them in well-named classes, functions, or namespaces. When all else fails, then prefixing the name may be necessary as a last resort.
- Add no more context to a name than is necessary. In an imaginary application called "Gas Station Deluxe", it is bad idea to prefix every class with
GSD
likeGSDAccountAddress
.
- The first rule of functions is that the should be small.
- The second rule of functions is that they should be smaller than that.
- Blocks within
if
statements,else
statements,while
statements, and so on should be one line long. Probably that line should be a function call. Not only does this keep the enclosing function small. but it also adds documentary value because the function called within the block can have a nicely descriptive name.
- Functions should do one thing. They should do it well. The should do it only.
- Functions that do one thing cannot be reasonably divided into many sections.
- In order to make sure our functions are doing "one thing", we need to make sure that the statements within our function are all at the same level of abstraction.
- Mixing levels of abstraction within a function is always confusing.
- The Stepdown Rule: We want the code to read like a top-down narrative. We want every function to be followed by those at the next level of abstraction so that we can read the program, descending on level of abstraction ata a time as we read down the list of functions.
- It's hard to make a
switch
statment that does on thing. By their nature,switch
statements always doN
things.
public Money calculatePay(Employee e) throws InvalidEmployeeType {
switch(e.type)
case COMMISSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calculateHourlyPay(e);
case SALARIED:
return calculateSalariedPay(e);
default:
throw new InvalidEmployeeType(e.type);
}
- There are several problems with this function: it's large, it does more than one thing, it has more than one reason to change, it must change whenever new types are added.
- The solution to this problem is to bury the
switch
statement in the basement of anABSTRACT FACTORY
, and never let anyone see it. The factory will use theswitch
statment to create appropriate instances of the derivatives ofEmployee
, and the various functions, such ascalculatePay
,isPayday
, anddeliverPay
, will be dispatched polymorphically through theEmployee
interface.
- The smaller and more focused a function is, the easier it is to choose a descriptive name.
- A long descriptive name is better than a short enigmatic name.
- Don't be afraid to spend time choosing a name. Indeed, you should try several diffent names and read the code with each in place.
- Be consitent in your names. Use the same phrases, nouns, and verbs in the function names you choose for your modules. For example:
includeSetupAndTeardownPages
,includeSetupPages
,includeSuiteSetupPages
.
- The ideal number of arguments for a function is zero(niladic). Next comes one(monadic), followed closely by two(dyadic). Three arguments(triadic) should be avoided where possible.
- More that three(polyadic) arguments require very special justification - and then shouldn't be used anyway.
- Arguments are even harder for a testing point of view.
- Using an output argument instead of a return value is confusing and hard to understand. Avoid them.
- There are two very common reasons to pass a single argument into a function.
- You may be asking a question about that argument, as in
boolean fileExists("MyFile")
- Or you may be operating on that argument, transforming it into something else and returning it. For example,
InputStream fileOpen("MyFile")
transforms a file nameString
into anInputStream
return value. - A somewhat less common, but still very useful form for a monadic function is an event. In this form there is an input argument but no output argument. The overall program is meant to interpret the function call as an event and use the argument to alter the state of the system. For example,
void passwortAttemptFailedNtime(int attempts)
.
- Flag arguments are ugly. Passing a boolean into a function is a truly terrible practive.
- It immediately complicates the signature of the method, loudly proclaiming that this function does more than one thing. It does one thing if the flag is
true
and another if the flag isfalse
.
- A function with two arguments is harder to understand than a monadic function.
- There are times when two arguments are appropriate. For example,
Point(0, 0)
is perfectly reasonable. Cartesian points naturally take two arguments. The two arguments in this case are ordered components of a single value. - When possible convert dyads into monads. For example,
writeField(outputStream, name)
can be transformed aswriteField(name)
by making theoutputStream
a member variable of the class so that you don't have to pass it. Or you might extract a new class likeFieldWriter
that takes theoutputStream
in its constructor and has awrite
method.
- Functions that take three arguments are significantly harder to understand than dyads. Think very carefully before creating a triad.
- When a function seems to need more than two or three arguments, it is likely that some of those arguments ought to be wrapped into a class of their own.
Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);
- In the case of monad, the function and argument should form a very nice verb/noun pair. For example,
write(name)
is very evocative. Whatever the "name" thing is, it is being "written".
- A function should not have hidden side effects. A function
checkPassword(String userName, String password)
should only check if the password is correct or not. It should not do any hidden tasks likeSession.initialize()
. - In this case we might rename the function
checkPasswordAndInitializeSession
, though that certainly violates Single Responsibility Principle.
- In the days before object oriented programming it was sometimes necessary to have output arguments. However, much of the need for output arguments disappears in OO languages because
this
is intended to act as an output argument. - Anything that forces you to check the function signature is equivalent to a double-take. It's cognitive break and should be avoided.
- Functions should either do something or answer something, but not both.
- Either your function should change the state of an object, or it should return some information about that object. Doing both often leads to confusion.
- Always separate the command from the query so that the ambiguity cannot occur.
- Returning error codes from command functions is a subtle violation of command query separation. It promotes commands being used as expressions in the predicates of
if
statements.
- Try/Catch blocks are ugly in thier own right. They confuse the structure of the code and mix error processing. So it's better to extract the bodies of the
try
andcatch
blocks out into functions of their own.
- Error handling is one thing. Thus, a function that handles errors should do nothing else.
- This implies that if the keyword
try
exists in a function, it should be the very first word in the function and that there should be nothing after thecatch/finally
blocks.
- Returning error codes usually implies that there is some class or enum in which all the error codes are defined.
- Classes like this are a dependency management. Many other classes must import and use them. Thus, when the
Error enum
changes, all those other classes need to be recompiled and redeployed. - When you use exceptions rather than error codes, then new exceptions are derivatives of the exception class. They can be added without forcing any recompilation or redeployment.
- Duplication may be the root of all evil in software.
- Encapsulate the duplicates and create smaller fucntions.
- This is DRY principle.
- Edsger Dijkstra's rules of structured programming states that every function, and every block within a function, should have one entry and one exit.
- Following these rules means that there should only be one
return
statement in a function, nobreak
orcontinue
statements in a loop, and never,ever, anygoto
statements. - It is only in larger functions that such rules provide significant benefits.
- If you keep your functions small, then the occasional multiple
return
,break
, orcontinue
statement does no harm.
- Don't expect to write functions in a perfect way from the start. Refactor, refactor and refactor!
- Master programmers think of systems as stories to be told rather that programs to be written.
- If out programming languages were expressive enough, or if we had the talent to subtly wield those languages to express out intent, we would not need comments very much -- perhabs not at all.
- The proper use of comments is to compensate for our failure to express ourself in code.
- So when you find yourself in a position where you need to write a comment, think it through and see whether there isn't some way to turn the tables and express yourself in code.
- Inaccurate comments are far worse than no comments at all. They delude and mislead.
- Truth can only be found in one place: the code. It is the only source of truly accurate information.
- One of the more common motivations for writing comments is bad code. We write a module and we know it is confusing and disorganized. So we tend to write comment.
- Rather than spend your time writing the comments that explain the mess you have made, spend it cleaning that mess.
- Don't:
if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))
Do:
if (employee.isEligibleForFullBenefits())
- Some comments are necessary or beneficial:
- Copyright and authorship statements are necessary and reasonable things to put into a comment at the start of each source file.
- It's sometimes useful to provide basic information with a comment. Still try to use the name of the function to convey the information where possible.
- Sometimes a comment can provide the intent behind a decision.
- In general it is better to find a way to make obscure arguments or return value clear in its own right; but when its part of the standard library, or in code that you cannot alter, then it is just helpful to translate the meaning of some obscure argument or return value into something that's readable.
- Sometimes it is useful to warn other programmers about certain consequences.
TODO
s are jobs that the programmer thinks should be done but for some reason can't do at the moment. It might be a reminder to delete a deprecated feature or a plea for someone else to look at a problem.
- A comment may be used to amplify the importance of something that may otherwise seem inconsequential.
- If you are writing a public API, then you should certainly write good javadocs for it.
- If you decide to write a comment, then spend the time necessary to make sure it is the best comment you can write.
- Don't write redundant comments.
- Don't write comments that are not accurate. Don't give false information to the reader.
- It's just plain silly to have a rule that says that every function must have a javadoc, or every variable must have a comment.
- Comments like this just clutter up the code, propagate lies, and lend to general confusion and disorganization.
- Sometimes people add a comment to the start of a module every time they edit it. These comments accumulate as a kind of journal, or log, of every change that has ever been made.
- Source code control systems can take care of these things. Don't add journal comments into your code.
- Noise comments restate the obvious and provide no new information.
/** The day of the month **/
priavate int dayOfMonth;
- Don't:
if (smodule.getDependSubsystems().contains(subSysMod.getSubSytem()))
Do:
ArrayList moduleDependees = smodule.getDependSubsystems();
String ourSubSystem = subSysMod.getSubSystem();
if (moduleDependees.contains(ourSubSystem))
- Sometimes programmers like to mark a particular position in a source file. They are called banners.
- Use banners carefully.
- Sometimes programmers put special comments on closing braces.
- The might make sense for long functions with deeply nested strutures. But small and encapsulated functions don't need comments. They are self explaining.
- Don't add bylines like
/* Added by Rick */
. - Source code control system can handle these things.
- Few practices are as odious as commenting-out code. Don't do this!!
- Again, source code control systems will remember the code for us. We don't have to comment it out any more. Just delete the code!
- If comments are going to be extracted by some tool (like Javadoc) to appear in Web page, then it should be the responsibility of that tool, and not the programmer, to adorn the comments with appropriate HTML.
- If you must write a comment, then make sure it describes the code it appears near. Don't offer systemwide information in the context of a local comment.
- Don't put interesting historical discussions or irrelevant descriptions of details into your comments.
- The purpose of a comment is to explain code that does not explain itself. It is a pity when a comment needs its own explanation.
- Short functions don't need much description. A well-chosen name for a small function that does one thing ususally better than a comment header.
- Generating javadoc pages for the classes and functions inside a system is not generally useful, and the extra formality of the javadoc comments amounts to little more than cruft and distraction.
- Error handling is important, but if it's impossible to see what the code does because of all of the scattered error handling, then it's wrong.
- If you return codes the the caller must check for errors immediately after the call. It clutters the caller.
- It's always better to throw an exception when you encounter an error.
- The calling code is cleaner and it's logic is not obscured by error handling.
- In a way,
try
blocks are like transactions. Yourcatch
block has to leave your program in a consistent state, no matter what happens in thetry
. - For this reason, it is a good practice to start with a
try-catch-finally
statement when you are writing code that could throw exceptions. - This helps you define what the user of that code should expect, no matter what goes wrong with the code that is executed in the
try
. - Try to write tests that force exceptions, and then add behavior to your handler to satisfy your tests. This will cause you to build the transaction scope of the
try
block first and will help you maintain the transaction nature of that scope.
- C#, C++, Python, Ruby don't have checked exceptions. Yet it is possible to write robust software in all of these languages.
- The price of checked exception is an Open/Closed Principle violation. If you throw a checked exception from a method in your code and the
catch
is three levels above, you must declare that exception in the signature of each method between you and the catch. - This means that a change at a low level of the software can force signature changes on many higher levels. The changed modules must be rebuilt and redeployed, even though nothing they care about changed.
- Encapsulation is broken because all functions in the path of a throw must know about details of that low-level exception.
- Checked exceptions can sometimes be useful if you are writing a critical library: You must catch them. But in general application development the dependency costs outweigh the benefits.
- Each exception that you throw should provide enough context to determine the source and location of an error.
- In Java, you can get a stack trace from an exception; however, a stack trace can't tell you the intent of the operation that failed.
- Create informative error messages and pass them along with your exceptions.
- Mention the operation that failed and the type of failure.
- If you are using logging in your application, pass along enough information to be able to log the error in your
catch
.
- There are many ways to classify errors. We can classify them by their source: Did they come from one component or another? Or their type: Are they device failures, network failures, or programming errors?
- However, when we define exception classes in an application, our most important concern shoudl be how they are caught.
- Bad approach:
ACMEPort port = new ACMEPort(12);
try {
port.open();
} catch (DeviceResponseException e){
reportPortError(e);
logger.log("Device response exception", e);
} catch (ATM1212UnlockedException e){
reportPortError(e);
logger.log("Unlock exception", e);
} catch (GMXError e){
reportPortError(e);
logger.log("Device response exception", e);
} finally {
.....
}
- Better appraoch:
LocalPort port = new LocalPort(12);
try {
port.open();
} catch (PortDeviceFailure e) {
reportError(e);
logger.log(e.getMessage(), e);
} finally {
.....
}
public class LocalPort {
private ACMEPort innerPort;
public LocalPort(int portNumber) {
innerPort = new ACMEPort(portNumber);
}
public void open() {
try {
innerPort.open();
} catch (DeviceResponseException e){
throw new PortDeviceFailure(e)
} catch (ATM1212UnlockedException e){
throw new PortDeviceFailure(e)
} catch (GMXError e){
throw new PortDeviceFailure(e)
} finally {
.....
}
}
}
- Wrapping third-party APIs is a best practice. When you wrap a third-party API, you minimize your dependencies upon it. You can move to a different library in the future without much penalty.
- Wrapping also makes it easier to mock out third-party calls when you are testing your own code.
- SPECIAL CASE PATTERN[Fowler]: Create a class or configure an object so that it handles a special case for you. When you do, the client code doesn't have to deal with exceptional behavior. That behavior is encapsulated in the special case object.
- When we return
null
, we are essentially creating work for ourselves and foisting problems upon our callers. All it takes is one missingnull
check to send an application spinning out of control. - Too many null checks in an application is a bad thing.
- Don't return
null
from a method. Consider throwing an exception or returning a SPECIAL CASE object instead. - If you are calling a null-returning method from a third-party API, consider wrapping that method that either throws an exception or returns a special case object.
- In many cases, special case objects are an esasy remedy. Imagine that you have code like this:
List<Employee> employees = getEmployees();
if(employees != null) {
for(Employee e : employees) {
totalPay += e.getPay();
}
}
If we change getEmployee
so that it returns an empty list we can clean up the code:
List<Employee> employees = getEmployees();
for(Employee e : employees) {
totalPay += e.getPay();
}
public List<Employee> getEmployees() {
if(..there are no employees..)
return Collections.emptyList();
}
- Returning
null
from methods is bad, but passingnull
into methods is worse. - Unless you are working within an API which expects you to pass
null
, you should avoid passingnull
in your code whenever possible.
- We can write robust clean code if we see error handling as a separate concern, something that is viewable independently of our main logic.
- According to Java Convention, a class should begin with a list of variables.
public static constants
should come first.- Then
private static
variables. - Followed by
private
instance variables. - There is seldom a good reason to have
public
variables.
- It's good to keep variables and utility functions
private
but sometimes we need to make themprotected
so that they can be accessed by a test. But loosening encapsulation is always a last resort.
- The first rule of classes is that they should be small.
- The second rule of classes is tht they should be smaller than that.
- With functions we measured size by counting physical lines. With classes we use a different measure. We count responsibilities
- The name of the class should describe what responsibilities it fulfills.
- We should also be able to write a brief description of the class in about 25 words, without using the words "if", "and", "or", or "but".
- The Single Responsibiliy Principle(SRP) states that a class or module should have one and only one reason to change.
- We want out systems to be composed of many small clsses, not a few large ones. Each small class encapsulates a single responsibility, has a single reason to change, and collaborates with a few others to achieve the desired system behaviors.
- Classes should have a small number of instance variables. Each of the methods of a class should manipulate one or more of those variables.
- In general the more variables a nethod manipulates the more cohesive that method is to its class.
- A class in which each variable is used by each method is maximally cohesive.
- We would like cohesion to be high. When cohesion is high, it means that the methods and variables of the class are co-dependent and hang together as a logical whole.
- You should try to separate the variables and methods into two or more classes such that the newclasses are more cohesive.
- When classes lose cohesion, split them!
- For most systems, change is continual.
- We want to structure our systems so tat we muck with as little as possible when we update them with new or changed features.
- In an ideal system, we incorporate new features by extending the system, not by making modifications to existing code.
- Open-Closed Principle(OCP) states that classes should be open for extension but closed for modification.
- Needs will change, therefore code will change.
- A Client class depending upon concerete details is at risk when those details change. We can introduce interfaces and abstract classes to help isolate the impact of those details.
- Dependencies upon concrete details challenges for testing our system.
- Instead of designing
Portfolio
so that it directly depends uponTokyoStockExchange
, we create an Interface,StockExchange
, that declares a single method.
public interface StockExchange {
Money currentPrice(String symbol)
}
public Portfolio {
private StockExchange exchange;
public Portfolio(StockExchange exchange) {
this.exchange = exchange;
}
}
- By minimizing coupling in this way, our classes adhere to another class design principle known as Dependency Inversion Principle(DIP). DIP says that our classes should depend upon abstraction, not on concrete details.