Foreword
- Introduction
- I write thousands of or milions of codes one day. And I proud of myself with my codes. However, in several days, I get rid of those codes and rewrite, because I cannot sure this is clean code. Duplicating this work, I can refactorize my codes, as a result, there leaves only requirements. Moreover, it helps our maintainment. But, it needs cost of time and effor. This book is not a solution, but, through Clean Code, I can listen experts’ opinions, reorganize my code style, and be more professional.
- Thanks to
- Benjamin Nuske, who is CEO of Delta Engine GmbH, presented me this book. I really thanks to him.
Chapter 1: Clean Code
There Will Be Code
- Programming makes machine to run and the program is associated with code.
- Abstraction of programming language is advancing.
- Code is one of language and is requirement what we express.
Bad Code
- Impeding other code
- Uncareful code
- Unorganized Code: Later equals Never
The Total Cost Of Owning A Mess
- One change makes two or three errors.
- Over time the mess becomes so big and so deep.
- And productivity approaches zero.
The Grand Redesign In The Sky
- Consuming your time to make clean code is associated with effectivity and professionality.
- Programming is a race and it can be longer.
Attitude
- Requirements’ change make bad code.
- Too tight schedule makes bad code.
- It is unprofessional for programmers to bend to the will of managers who don’t understand the risks of making messes.
The Primal Conundrum
- The only way to make the deadline is to keep the code as clean as possible at all times
The Art Of Clean Code?
- Writing clean code requires the disciplined use of a myriad little techniques applied through a painstakingly acquired sense of
cleanliness
. - The
Code-sense
will help that programmer choose the best variation and guide him or her to plot a sequence of behavior preserving transformations to get from here to there.
What Is Clean Code?
- elegant, pleasing, readable, to be enhance, easy to care, no duplication, one thing, expressiveness, tiny abstractions, beutiful
Schools Of Thought
- a clean variable name, a clean function, a clean class, etc.
We Are Authors
- Indeed, authors are responsible for communicating well with their readers.
- So making it easy to read actually makes it easier to write.
- If you want to go fast, if you want to get done quickly, if you want your code to be easy to write, make it easy to read.
The Boy Scout Rule
- Leave the caompground cleaner than you found it.
Prequel and Principles
- These include the Single Responsibility Principle(SRP), the Open Closed Principle(OCP), and the Dependency Inversion Principle(DIP) among others.
Chapter 2: Meaningful Names
Use Intention-Revealing Names
- Name should reveal intent.
- With these simple name changes, it’s not difficult to understand what’s going on.
Example 1
- Bad
- Good
Example 2
- Bad
- Good
Avoid Disinformation
- We should avoid words whose entrenched meanings vary from our intended meaning.
- Abbreviation could be disinformative. ex)
hp
,aix
andsco
- The word list menas something specific to programmers. Just add
-s
ex)accountList
→accounts
- Using inconsistent spellings is disinformation
- Don’t use single lower-case L or uppercase O as variable names. ex)
if(O == l)
Make Meaningful Distinctions
- If names must be different, then they should also mean something different.
- Noise words are another meaningless distinction. ex)
Info
andData
Use Pronounceable Names
- Single-letter names and numeric constants have a particular problem in that they are not easy to locate across a body of text.
- Programming is a social activity.
Example 1
- Bad
- Good
Avoid Encodings
- Encoding type or scope information into names simply adds an extra burden of deciphering.
- Encoded names are seldom pronounceable and are easy to mis-type.
Hungarian Notation
- Nowdays Hungarian Notation and other forms of type encoding are simply impediments.
- They make it harder to change the name or type of variable, function, or class.
- They make it ahrder to read the code.
- They create the possibility that the encoding system will mislead the reader
Member Prefixes
- You should be using an editing environment that highlights or colorizes members to make them distinct.
- The more we read the code, the less we see the prefixes.
Example 1
- Bad
- Good
Interfaces and Implementations
- The preceding
I
, so common in today’s legacy wads, is a distraction at best and too much information at worst. - Don’t
IShapeFactory
, useShapeFactoryImp
orCshapeFactory
Avoid Mental Mapping
- This is a problem with single-letter variable names.
- It’s just a place holder that the reader must mentally map to the actual concept.
- Professionals use their powers for good and write code that others can understand.
Class Names
- Classes and objects should have noun or noun pharse names.
Method Names
- Methods should have verb or berb phrase name.
- Predicates should be named for their value and prefixed with
get
,set
, andis
.
Example 1
- Good
- when constructors are overloaded, use static factory methods with names that describe the arguments. ex)
var fulcrumPoint = Complex.FromRealNumber(23.0);
Don’t be Cute
- Choose clarity over entertainment value.
- Say what you mean. Mean what you say.
Pick One Word per Concept
- It’s confusing to have
fetch
,retrieve
, andget
as equivalent methods of different classes. - The function names have to stand alone, and they have to be consistent in order for you to pick the correct method without any additional exploration.
- A consistent lexicon is a great boon to the programmers who must use your code.
Don’t Pun
- Avoid using the same word for two purpose.
- Out goal, as authors, is to make our code as easy as possible to understand.
- We wnat our code to be a quick skim, not an intense study.
Use Solution Domain Names
- Go ahead and use computer science terms, algorithm names, pattern names, math terms, and so forth. ex)
AccountVisitor
→JobQueue
- Choosing technical names for those things is usually the appropriate course.
Use Problem Domain Names
- Separating solution and problem domain concepts is part of the job of a good programmer and designer.
- The code that has more to do with problem domain concepts should have names drawn from the problem domain.
Add Meaningful Context
- Prefixing the name may be necessary as a last resort.
- The improvement of context also allows the algorithm to be made much cleaner by breaking it into many smaller functions.
Example 1
- Bad
- Good
Don’t Add Gratuitous Context
- Shorter names are generally better than longer ones, so long as they are clear.
- Add no more context to a name than is necessary.
- The resulting names are more precise, which is the point of all naming.
Final Words
- We do not share that fear and find that we are actually grateful when names changes for the better.
- You will probably end up surprising someone when you rename, just like you might with any other code improvement.
- If you are maintaining someone else’s code, use refactoring tools to help resolve these problems.
- It will pay off in the short term and continue to pay in the long term.
Chapter 3: Functions
- Functions are the first line of organization in any program.
Example 1
- Bad
- Good
Small!
- The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that.
Example 1
- Good
Blocks and Indenting
- This implies that the blocks within
if
statements,else
statements,while
statements, and so on should be one line long. - The indent level of a function should not be greater than one or two.
Do One Thing
- We can describe the function by describing it as a brief
TO
paragraph - Another way to know that a function is doing more then one thing is if you can extract another function from it with a name that is not merely a restatement of its implementation.
Sections within Functions
- Functions that do one thing cannot be reasonably divided into sections.
One Level of Abstraction per Function
- 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.
Reading Code from Top to Bottom: The Stepdown Rule
- We want every function to be followed by those at the next level of abstraction so that we can read the program, descending one level of abstraction at a time as we read down the list of functions.
- We want to be able to read the program as though it were a set of TO paragraphs, each of which is describing the current level of abstraction and referencing subsequent TO paragraphs at the next level down.
- Making the code read like a top-down set of TO paragraphs is an effective technique for keeping the abstraction level consistent.
Switch Statements
- By the nature,
switch
statements always do N things. - Unfortunately we can’t always avoid
switch
statements, but we can make sure that eachswitch
statement is buried in a low-level class ans is never repeated with polymorphism. - My general rule for
switch
statements is that they can be tolerated if they appear only once, are used to create polymorphic objects, and are hidden behind an inheritance relationship so that the rest of the system can’t see them.
Example 1
- Bad
- Good
Use Descriptive Names
- 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.
- A long descriptive name is better than a long descriptive comment.
- Choosing descriptive names will clarify the design of the module in your mind and help you to improve it.
- Use the same phrases, nouns and verbs in the function names you choose for your modules.
Function Arguments
- The ideal number of arguments for a function is zero(niladic). Next come one(monadic), follwed closely by two(dyadic). Three arguments(triadic) should be avoided where possible.
- They take a lot of conceptual power. The argument is at a different level of abstraction than the function name and forces you to know a detail that isn’t particularly important at that point.
- Output arguments are harder to understand than input arguments. When we read a function, we are used to the idea of informatio going
in
to the function through arguments andout
through the return value.
Common Monadic Forms
-
function fileExists(MyFile)
,function fileOpen(MyFile)
- Event
Flag Arguments
- 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 is false.
Dyadic Functions
- The parts we ignore are where the bugs will hide.
Point p = new Point(0, 0;
- You should be aware that they come at a cost and should take advantage of what mechanisms may be available to you to convert them into monads.
Triads
- I suggest you think very carefully before creating a triad.
Argument Objects
- 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.
Example 1
- Bad
- Good
Argument Lists
-
String.format
is actually dyadic. - Functions that take variable arguments can be monads, dyads, or even triads. But it would be a mistake to five them more arguments than that.
Verbs and Keywords
- In the case of a monad, the function and argument should form a very nice verb/noun pair.
WrieteField(name)
Have No Side Effects
- Your function promises to do one thing, but it also does other hidden things.
- If you must have a temporal coupling, you should make it clear in the name of the function.
Example 1
- Bad
Output Arguments
- Arguments are most naturally interpreted as input to a function.
- Anything that forces you to check the function signature is equivalent to a double-take. It’s a cognitive break and should be avoided.
- Much of the need for output arguments disappears in OO languages because
this
is intended to act as an output argument. - In general output arguments should be avoided. If your function must change the state of something, have it change the state of its owning object.
Command Query Separation
- Function 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.
-
arrtivuteExists("username")
,setAttribute("username","unclebob")
Prefer Exceptions to Returning Error Codes
- Returning error codes from command functions is a subtle violation of command query separtation. It promotes commands being used as expressions in the predicates of
if
statements. - On the other hand, if you use exceiptions instead of returned error codes, then the error processing code can be separated from the happy path code and can be simplified.
Extract Try/Catch Blocks
- It is better to extract the boides of the
try
andcatch
blocks out into functions of their own.
Example 1
- Good
Error Handling Is One Thing
- 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.
Don’t Repeat Yourself
- This duplication was remedied by the
include
method - Consider also how object-oriented programing werves to concentrate code into base classes that would otherwise be redundant.
- Structured programming, Aspect Oriented PRogramming, Component Oriented Programming, are all, in part, strategies for eliminating duplication.
- It would appear that since the invention of the subroutine, innovations in software development have been an ongoing attempt to eliminate duplication from our source code.
Structured Programming
- Dijkstra said 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. - If you keep your functions small, then the occasional multiple
return
,break
, orcontinue
statement does no harm and can sometimes even be more expressive than the single-entry, single-exit rule. - On the other hand,
goto
only makes sense in large functions, so it should be avoided.
How Do You Write Functions Like This?
- I also have a suite of unit tests that cover every one of those clumsy lines of code.
- I massage and refine that code, splitting out functions, changing names, eliminating duplication.
- I shrink the methods and reorder them.
- Sometimes I break out whole classes, all the while keeping the tests passing.
Conclusion
- Functions are the verbs of that language, and classes are the nouns.
- But, never forget that your real goal is to tell the story of the system, and that the functions you write need to fit cleanly together into a clear and precise language to help you with that telling.
Chapter 4: Comments
- If our programming languages were expressive enough, or if we had the talent to subtly wield those languages to express our intent, we would not need comments very much.
- Comments are always failures.
- Programmers can’t realistically maintain them.
- Therefore, though comments are sometimes necessary, we will expand significant energy to minimize them.
Comments Do Not Make Up for Bad Code
- Clear and expressive code with few comments is far superior to cluttered and complex code with lots of comments.
Explain Yourself in Code
- There are certainly times when code makes a poor vehicle for explanation.
- It takes only a few seconds of thought to explain most of your intent in code.
- In many cases it’s simply a matter of creating a function that says the same thing as the comment you want to write.
Example 1
- Bad
- Good
Good Comments
- Some comments are necessary or beneficial.
- Keep in mind, however, that the only truly good comment is the comment you found a way not to write.
Legal Comments
- Copyright and authorship statements are necessary and reasonable things to put into a comment at the start of each source file.
- Where possible, refer to a standard license or other external document rather than putting all the terms and conditions into the comment.
Informative Comments
- Still, it might have been better, and clearer, then the comment would likely habe been superfluous.
Explanation of Intent
- Sometimes a comment goes beyond just useful information about the implementation and provides the intent behind a decision.
Example 1
- Bad
- Good
Clarification
- When its part of the standard library, or in code that you cannot alter, then a helpful clarifying comment can be useful.
- Before writing comments like this, take care that there is no better way, and then take even more care that they are accurate.
Warning of Consequences
- It will prevent some overly eager programmer from using a static initializer in the name of efficiency.
Example 1
- Good
- Good
TODO Comments
- It is sometimes resonable to leave “To do” notes in the form of
//TODO
comments. - The
TODO
comments explains why the function has a degenerate implementation and what that function’s future should be. -
TODOs
are jobs that the programmer thinks should be done, but for some reason can’t do at the moment. - So scan through them regularly and eliminate the ones you can.
Example 1
- Good
Amplification
- A comment may be used to amplify the importance of something that may otherwise seem inconsequential.
Example 1
- Good
Bad Comments
- Usually they are crutches or excuses for poor code or justifications for insufficient decisions, amounting to little more than the programmer talking to himself.
Mumbling
- If you decide to write a comment, then spend the time necessary to make sure it is the best comment you can write.
- Any commment that forces you to look in another module for the meaning of that comment ahs failed to communicate to you and is not worth th bits if consumes.
Example 1
- Bad
Redundant Comments
- It’s certainly not more informative than the code.
Example 1
- Bad
Misleading Comments
- This subtle bit of misinformation, couched in a comment that is harder to read than the body of the code, coyld cause another programmer to blithely call this function in the expectation.
- That poor programmer would then find himself in a debugging session trying to figure out why his code executed so slowly.
Mandated Comments
- It is just plain silly to have a rule that says every function must have a javadoc, or every variable must have a comment.
- This clutter adds nothing and serves only to obfuscate the code and create the potential for lies and misdirection.
Example 1
- Bad
Journal Comments
- These comments accumulate as a kind of journal, or log, of every change that has ever been made.
- They should be completely removed.
Noise Comments
- They restate the obvious and provide no new information.
- Eventually the comments begin to lie as the code around them changes.
- Rather than venting in a worthless and noisy comment, the programmer should have recognized that his frustration could be resolved by improving the structure of his code.
- Replace the temptation to create noise with the determination to clean your code.
Example 1
- Bad
Example 2
- Bad
Example 3
- Bad
Example 4
- Bad
- Good
Scary Noise
- They are just redundant noisy comments written out of some misplaced desire to provide documentation.
Example 1
- Bad
Don’t Use a Comment When You Can Use a Function or a Variable
- The author of the original may have written the comment first and then written the code to fulfill the comment.
### Example 1
- Bad
- Good
Position Markers
- So use them very sparingly, and only when the benefit is significant.
- If you overuse banners, they’ll fall into the background noise and be ignored.
Example 1
- Bad
Closing Brace Comments
- Although this might make sense for long functions with deeply nested structures, it serves only to clutter the kind of small and encapsulated functions that we prefer.
Example 1
- Bad
Attributions and Bylines
- Again, the source code control system is a better place for this kind of information.
Example 1
- Bad
Commented-Out Code
- Few practices are as odious as commenting-out code.
- Other will think it is there for a reason and is too important to delete.
- Just delete the code.
Example 1
- Bad
Example 2
- Bad
HTML Comments
- It makes the comments hard to read in the one place where they should be easy to read=the ediot/IDE.
Example 1
- Bad
Nonlocal Information
- If you must write a comment, then make sure it describes the code it appears near.
Example 1
- Bad
Too Much Information
- Don’t put interesting historical discussions or irrelevant descriptions of details into your comments.
Example 1
- Bad
Inobvious Connection
- The connection between an comment and the code it describes should be obvious.
- The purpose of a comment is to explain code that does not explain itself.
Example 1
- Bad
Function Headers
- Short functions don’t need much description.
- A well-chosen name for a small function that does one thing is usually better a comment header.
Chapter 5: Formatting
- You should take care that your code is nicely formatted.
- You should choose a set of simple rules that govern the format of your code, and then you consistently apply those rules.
The Purpose of Formatting
- Code formatting is about communication, and communication is the professional developer’s first order of business.
- The coding style and readabilty set precedents that continue to affect maintainability and extensibility long after the original code has been changed beyond recognition.
Vertical Formatting
- It turns out that there is a huge range of sizes and some remarkable differences in style.
- small files are usually easier to understand than large files are.
The Newspaper Metaphor
- You read it vertically.
- The name should be simple but explanatory.
- Detail should increase as we move downward, until at the end we find the lowest level functions and details in the source file.
Vertical Openness Between Concepts
- Each line represents an expression or a clause, and each group of lines represents a complete thought. Those thoughts should be separated from each other iwht blank lines.
Example 1
- Bad
- Good
Vertical Density
- So lines of code that are tightly related appear vertically dense.
Example 1
- Bad
* Good
Vertical Distance
- Concepts that are closely related should be kept vertically close to each other.
- Clearly this rule doesn’t work for concepts that belong in separate files.
- But then closely related concepts should not be separated into different files.
- Indeed, this is one of the reasons that protected variables hould be avoided.
-
For those concepts that are so closely related that they belong in the same source file, their vertical separation should be a measure of how importnat each is to the understandability of the other.
- Variable Declarations
- Variables should be declared as close to their usage as possible.
- Because our functions are very short, local variables should appear at the top of each function.
- Control variables for loops should usually be declared within the loop statement.
- In rare cases a variable might be declared at the top of a block or just before a loop in a longish function.
Example 1
- Good
- Good
- Good
- Instance variables
- Instance variables, on the other hand, should be declared at the top of the class.
- The important thing is for the instance variables to be declared in one well-known place.
Example 1
- Bad
- Dependent Functions
- If one function calls another, they should be vertically close, and the caller should be above the callee, if at all possible.
- It was better to pass that constant down from the place where it makes sense know it to the place that actually uses it.
Example 1
- Good
- Conceptual Affinity
- The stronger that affinity, the less vertical distance there shold be between them.
- Affinity might be caused because a group of functions perform a similar operation.
Example 1
- Good
Vertical Ordering
- In general we want function call dependencies to point in the downward direction. That is, a function that is called should be below a function that does the calling.
- We expect the low-level details to come last. This allows us to skim source files, getting the gist from the first few functions, without having to immerse ourselves in the details.
Horizontal Formatting
- This suggests that we should strive to keep our lines short.
Horizontal Openness and Density
- We use horizontal white space to associate things that are strongly related and disassociate things that are more weakly related.
- Assignment statements have two distinct and major elements: the left side and the right side.
- I separate arguments within the function call parenthesis to accentuate the comma and show that the arguments are separte.
- Another use for white space is to accentuate the precedence of operators.
- The terms are separated by white space because addition and subtraction are lower precedence.
Example 1
- Good
- Good
Horizontal Alignment
- Nowadays I prefer unaligned declarations and assignments, because they point out an important deficiency.
Example 1
- Bad
- Good
Indentation
- There is information that pertains to the file as a whole, to the individual classes within the file, to the methods within the classes, to the blocks within the methods, and recursively to the blocks within the blocks.
- To make this hierarchy of scopes visible, we indent the lines of source code in proportion to their position in the hierarchy.
- Without indentation, programs would be virtually unreadable by humans.
Example 1
- Bad
- Good
- Breaking Indentation
- It is sometimes tempting to break the indentation rule for short
if
statemtns, shortwhile
loops, or short functions. - I avoid colapsing scopes down to one line.
- It is sometimes tempting to break the indentation rule for short
Example 1
- Bad
- Good
Dummy Scopes
- I make sure that the dummy body is properly indented and surrounded by braces.
- Unless you make that semicolon visible by indenting it on it’s own line, it’s jut too hard to see.
Example 1
- Good
Team Rules
- A team of developers should agree upon a single formatting style, and then every member of that team should use that style.
- Remember, a good software system is composed of a set of documents that read nicely.
Chapter 6: Objects and Data Structures
Data Abstraction
- The methods enforce an access policy.
- Hiding implementation is about abstraction.
- It exposes abstract interfaces that allow its users to manipulate the essence of the data, without having to know its implementation.
- In the abstract case you have no clue at all about the form of the data.
- We do not want to expose the details of our data. Rather we want to express our data in abstract terms.
Example 1
- Bad
- Good
Example 2
- Bad
- Good
Data/Object Anti-Symmetry
- Objects hide their data behind abstractions and expose functions that perate on that data.
- Data structure espose their data and have no meaningful functions.
- Procedural code makes it hard to add new data structures because all the functions must change.
- OO code makes it hard to add new functions because all the classes must change.
Example
- Procedural
- Polymorphic
The Law of Demeter
- A method f of a class C should only call the methods these:
- C
- An object created by f
- An Object passed as an argument to f
- An Object held in an instance variable of C
Example
- Bad
Train Wrecks
- It is usually best to split them up.
- This issue would be a lot less confusing if data structures simply had public variables and no functions, whereas objects had private variables and public functions.
Example
- Good
- Good
Hybrids
- They have functions that do significant things, and they also have either public variables or public accessors and mutators that, for all intents and purposes, make the private variables public, tempting other external functions to use those variables the way a procedural program would use a data structure.
- Such hybrids make it hard to add new functions but also make it hard to add new data structures.
Hiding Structure
- We should not be asking it about its internals.
Example
- Good
Data Transfer Objects
- The quintessential form of a data structure is a class with public variables and no functions.
- DTOs are very useful structures, especially when communication with databases or parsing messages from sockets, and so on.
Active Record
- They are data structures with public variables; but they typically have navigational methods like
save
andfind
.
Chapter 7: Error Handeling
Use Exceptions Rather Than Return Codes
- The caller must check for errors immediately after the call.
- For this reason it is better to throw an exception when you encounter an error.
- The code is better because two concerns that were tangled, the algorithm for device shutdown and error handling, are now separated.
Example
- Bad
- Good
Write Your Try-Catch-Finally
Statement First
- When you execute code in the
try
portion of atry-catch-finally
statement, you are stating that execution can abort at any point and then resume at thecatch
. - Your
catch
has to leave your program in a consistent state, no matter what happens in thetry
.
Use Unchecked Exceptions
- Given that the purpose of exceptions is to allow you to handle errors at a distance, it is a shame that checked exceptions break encapsulation in this way.
Provide Context with Exceptions
- Each exception that you throw should provide enough context to determine the source and location of an error.
- Create informative error messages and pass them along with your exceptions.
Define Exception Classes in Therms of a Caller’s Needs
- When we define exception classes in an pplication, our most important concern should be how they are cought.
- Wrapping also makes it easier to mock out third-party calls when you are testing your own code.
- One final advantage of wrapping is that you aren’t tied to a particular vendor’s API design choices.
Examples
- Bad
- Good
Define the Normal Flow
- You 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.
### Example
- Bad
- Good
Dont’t Return Null
- If you are tempted to 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 with a method that either throws an exception or returns a special case object.
- If you code this way, you will minimize the chance of
NullPointerExceptions
and your code will be cleaner.
Dont’t Pass Null
- In most programming languages there is no good way to deal with a
null
that is passed by a caller accidentally. - Because this is the case, the rational approach is to forbid passing
null
by default. - When you do, you can code with the knowledge that a
null
in an argument list is an indication of a problem, and end up with far fewer careless mistaken.
Chapter 8: Boundaries
Using Third-Party Code
- If you use a boundary interface, keep it inside the class, or close family of classes, where it is used.
- Avoid returning it from, or accepting it as an argument to, public APIs.
Exploring and Learning Boundaries
- We could write some tests to explore our understanding of the third-party code.
- Jim Newkirk calls such tests
learning tests
.
Learning Test Are Better Than Free
- The learning tests were precise experiments that helped increase our understanding.
- Not only are learning tests free, they have a positive return on investment.
- Learning tests verify that the third-party packages we are using work the way we expect them to.
- Whether you need the learning provided by the learning tests or not, a clean boundary should be supported by a set of outbound tests that exercise the interface the same way the production code does.
Using Code That Does Not Yet Exist
- There are often places in the code where our knowledge seems to drop off the edge.
- Sometimes what is on the other side of the boundary is unknowable.
- Sometimes we choose to look no farther than the boundary.
Clean Boundaries
- Good software designs accommodate change without huge investments and rework.
- When we use code that is out of our control, special care must be taken to protect our investment and make sure future change is not too costly.
- Code at the boundaries needs clear separation and tests that define expectations.
- We should avoid letting too much of our code know about the third-party particulars.
- Either way our code speaks to us better, promotes internally consistent usage across the boundary, and has fewer maintenance points when the third-party code changes.
Chapter 9 : Unit Tests
- The Agile and TDD(Test Driven Development) movements have encouraged many programmers to write automated unit tests, and more are joining their tanks every day.
The Three Laws of TDD
- You may not write production code until you have written a failing unit test.
- You may not write more of a unit test than is sufficient to fail, and not compiling is failing.
- You may not write more production code than is sufficient to pass the currently failing test.
Keeping Test Clean
- The dirtier the tests, the harder they are to change.
- Test code is just as important as production code.
- It must be kept as clean as production code.
Tests Enable the -ilities
- It is
unit tests
that keep our code flexible, maintainable and reusable. - Having an automated suite of unit tests that cover the production code is the key to keeping your design and architecture as clean as possible.
- Tests enable all the -ilities, because tests enable change.
Clean Tests
- Readability is perhaps even more important in unit tests than it is in production code.
- The same thing that makes all code readable: clarity, simplicity, and density of expression.
- Notice that the vast majority of annoying detail has been eliminated.
- The tests get right to the point and use only the data types and functions that they truly need.
Example
- Bad
- Good
Domain-Specific Testing Language
- They are a testing language that programmers use to help themselves to write their tests and to help those who must read those tests later on.
- This testing API is not designed up front; rather it evolves from the continued refactoring of test code that has gotten too tainted by obfuscating detail.
A Dual Standard
- After all, it runs in a test environment, not a production environment, and those two environment have very different needs.
- There are things that you might never do in a production environment that are perfectly fine in a test environment.
Example 1
- Bad
- Good
Example 2
- Bad
- Good
One Assert per Test
-
given-when-then
makes the tests even easier to read. - We can eliminate the duplication by using the template method pattern and putting the
given/when
parts in the base class, and thethen
parts in different derivatives. - Or we could create a completely separate test class and put the
given
andwhen
parts in the@Before
function, and thewhen
parts in each@Test
function.
Example
- Good
Single Concept per Test
- Merging them all together into the same function forces the reader to figure out why each each section is there and what is being tested by that section.
- So probably the best rule is that you should minimize the number of asserts per concept and test just one concept per test function.
Example
F.I.R.S.T
- First : Tests should be fast.
- Independent : Test should not depend on each other.
- Repeatable : Test should be repeatable in any environment.
- Self-Validating: The tests should have a boolean output.
- Timely : The tests need to be written in a timely fashion.
Chapter 10: Classes
Class Organization
- public functions should follow the list of varialbes.
- We like to put the private utilities called by a public function right after the public function itself.
- This follows the stepdown rule and helps the program read like a newspaper article.
Encapsulation
- Loosening encapsulation is always a last resort.
Classes Should Be Samll!
- Smaller is the primary rule when it comes to designing classes.
- The name of a class should describe what responsibilities it fulfills.
The Single Responsibility Principle
- Classes should have one responsibility-onereason to change.
- Trying to identify responsibilities(reasons to change) often helps us recognize and create better abstractions in our code.
- SRP is one of the more important concept in OO design.
- A system with many small classes has no more moving parts than a system with a few large classes.
- 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.
Example
- Good
Cohesion
- In general the more variables a method manipulates the more cohesive that method is to its class.
- The strategy of keeping functions small and keeping parameter lists short can sometimes lead to a proliferation of instance variables that are used by a subset of methods.
- You should try to separate the variables and methods into two or more classes such that the new classes are more cohesive.
Example
- Good
Maintaining Cohesion Results in Many Small Classes
- Breaking a large function into many smaller functions often gives us the opportunity to split several smaller classes out as well.
- This gives our program a much better organization and a more transparent structure.
Example
- Bad
- Good
Organizing for Change
- In a clean system we organize our classes so as to reduce the risk of change.
- Classes should be open for extension but closed for modification.
Example
- Bad
- Good
Isolating from Change
- A client class depending upon concrete details is at risk when those details change.
- The lack of coupling means that the elements of our system are better isolated from each other and from change.
- This isolation makes it easier to understand each element of the system.
- By minimizing coupling in this way, our classes adhere to another class design principle known as the Dependency Inversion Principle(DIP).
- In essence, the DIP says that our classes should depend upon abstractions, not on concrete details.
- This abstraction isolates all of the specific details of obtaining such a price, including from where that price is obtained.
Chapter 11: Systems
Separate Constructing a System from Using it
- We can’t compile without resolving these dependencies, even if we never actually use an object of this type at runtime!
- We should modularize this process separately from the normal runtime logic and we should make sure that we have a global, consistent strategy for resolving our major dependencies.
Separation of Main
- One way to separate construction from use is simply to move all aspects of construction to
main
, or modules called bymain
and to design the rest of the system assuming that all objects have been constructed and wired up appropriately. - Notice the direction of the dependency arrows crossing the barrier between
main
and the application.
Factories
- Sometimes, of course, we need to make the application responsible for when an object gets created.
Dependency Injection
- A powerful mechanism for separating construction from use is
Dependency Injection(DI)
, the application ofInversion of Control(Ioc)
to dependency management. - Inversion of Control moves secondary responsibilities from an object to other objects that are dedicated to the purpose, thereby supporting the
Single Responsibility Principle
. - The class takes no direct steps to resolve its dependencies; it is completely passive. Instead, it provides setter methods or constructor arguments(or both) that are used to
inject
the dependencies.
Scling Up
- We should implement only today’s stories, then refactor and expand the system to implement new stories tomorrow.
- Software systems are unique compared to physical system. Their architectures can grow incrementally, if we maintain the proper separation of concerns.
Cross-Cutting Concerns
- Note that concerns like persistence tend to cut across the natural object boundaries of a domain.
- In AOP(aspect-oriented programming), modular constructs called aspects specify which points in the system should have their behavior modified in some consistent way to support a particular concern.
Test Drive the System Architecture
- If you can write your application’s domain logic, then it is possible to truly test drive your architecture.
- It is not necessary to do a
Bid Design Up Front(BDUF)
. - Although software has its own physics, it is economically feasible to make radical change, if the structure of the software separates its concerns effectively.
- A good API should largely disappear from view most of the time, so the team expends the majority of its creative efforts focused on the user stories being implemented.
Optimize Decision Making
- We will have that much less customer feedback, mental reflection on the project, and experience with our implementation choices, if we decide too soon.
Use Standards Wisely, When They Add Demenstrable Value
- Standards make it easier to reuse ideas and components, recruit people with relevant experience, encapsulate good ideas, and wire components together. However, the process of creating standards can sometimes take too long for industry to wait, and some standards lose touch with the real needs of the adopters they are intended to serve.
Systems Need Domain-Specific Language
- In software, there has been renewed interest recently in creating
Domain-Specific Languages(DSLs)
, which are separate, small scripting languages or APIs in standard languages that permit code to be written so that it reads like a structured form of prose that a domin expert might write. - A good DSL minimizes the “communication gap” between a domain concept and the code that implements it, just as agile practices optimize the communications within a team and with the project’s stakeholders.
- DSLs, when used effectively, raise the abstraction level above code idioms and design patterns.
Chapter 12: Emergence
Getting Clean via Emergent Design
- According to Kent Beck, a design is simple if it follows these rules:
- Runs all the tests
- Contains no duplication
- Expresses the intent of the programmer
- Minimizes the number of classes and methods
Simple Design Rule 1: Funs All the Tests
- A system that is comprehensively tested and passes all of its tests all of the time is a testable system.
- Fourtunately, making our systems testable pushes us toward a design where our classes are small and single purpose.
- Remarkably, following a simple and obvious rule that says we need to have tests and run them continuously impacts our system’s adherence to the primary OO goals of low coupling and high cohesion.
Simple Design Rules 2-4: Refactoring
- During this refactoring step, we can apply anything from the entire body of knowledge about good software design.
- This is also where we apply th final three rules of simple design.
No Duplication
- It represents additional work, additional risk, and additional unnecessary complexity.
- Creating a clean system requires the will to eliminate duplication, even in just a few lines of code.
- This “reuse in the small” can cause system complexity to shrink dramatically.
- Understanding how to achieve reuse in the small is essential to achieving reuse in the large.
Example 1
- Bad
- Good
Example 2
- Bad
Expressive
- The majority of the cost of a software project is in long-term maintenance.
- The clearer the author can make the code. the less time others will have to spend understanding it.
- This will reduce defects and shrink the cost of maintenance.
- Choose better names, split large functions into smaller functions, and generally just take care of what you’ve created.
Minimal classes and Methods
- High class and method counts are sometimes the result of pointless dogmatism.
- Such dogma should be resisted and a more pragmatic aproach adopted.
- Our goal is to keep our overall system small while we are also keeping our functions and classes small.
Chapter 13: Concurrency
Why Concurrency?
- It helps us decouple what gets done from when it gets done.
- Decoupling what from when can dramatically improveboth the throughput and structures of an pplication.
- We could improve the performance by using a multithreaded algorithm that hits more than one Web site at a time.
- We could improve the response time of this system by handling many users concurrently.
- Perhaps each data set could be processed on a different computer, so that many data sets are being processed in parallel.
Challenges
- The surprising third result occurs when the two threads step on each other.
- To really answer that question, we need to understand what the Just-In-Time Compiler does with the generated byte-code, and understand what the Java memory model considers to be atomic.
Concurrency Defense Principles
Single Responsibility Principle
- The SRP states that a given method/class/component should have a single reason to change.
- Concurrency design is complex enough to be a reason to change in it’s own right and therefore deserves to be separated from the rest of the code.
Corollary: Limit the Scope of Data
- One solution is to use the
synchronized
keyword to protect a critial section in code that the shared object.
Corollary: Use Copies of Data
- In some situations it is possible to copy objects and treat them as read-only.
Corollary: Threads Should Be as Independent as Possible
- Each thread processes one client request, with all of its required data coming from an unshared source and stored as local variables.
Know Your Library
Thread-Safe Collections
Know Your Execution Models
- Bound Resources: Resources of a fixed size or number used in a concurrent environment. Examples include database connections and fixed-size read/write buffers.
- Mutual Exclusion: Only one thread can access shared data or a shared resource at a time.
- Starvation: One thread or a group of threads is prohibited from proceeding for an excessively long time or forever. For Exmaple, always letting fast-running threads through first could starve out longer running threads if there is no end to the fast-running threads.
- Deadlock: Two or more threads waiting for each other to finish. Each thread has a resource that the other thread requires and neither can finish until it gets the other resource.
- Livelock: Threads in lockstep, each trying to do work but finding another “in the way”. Due to reasonance, threads continue trying to make progress but are unable to for an excessively long time or forever.
Producer-Consumer
- Bound resources: This means producers must wait for free space in the queue before writing and consumers must wait until there is something in the queue to consume.
Readers-Writers
- Emphasizing throughput can cause starvation and the accumulation of stale information.
- If there are frequent writers and they are given priority, throughput will suffer.
- Finding that balance and avoiding concurrent update issues is what the provlem addresses.
Dining Philosophers
- Learn these basic algorithms and understand their solutions.
Beware Dependencies Between Synchronized Methods
- Client-Based Locking: Have the client lock server before calling the first method and make sure the lock’s extent includes code calling the last method.
- Server-Based Locking: Within the server create a method that locks the server, calls all the methods, and then unlocks. Have the client call the new method.
- Adapted Server: create an intermediary that performs the locking. This is an example of server-based locking, where the original server cannot be changed.
Keep Synchronized Sections Small
- The
Synchronized
keyword introduces a lock. - Locks are expensive because they create delays and add overhead.
- Critical sections must be guarded.
- So we want to design our code with as few critical sections as possible.
- Keep your synchronized sections as small as possible.
Writing Correct Shut-Down Code Is Hard
- Situations like this are not at all uncommon. So if you must write concurrent code that involves shutting down gracefully, expect to spend much of your time getting the shut-down to happen correctly.
- Think about shut-down early and get it working early. It’s going to take longer than you expect. Review existing algorithms because this is probably harder than you think.
Testing Threaded Code
- Write tests that have the potential to expose problems and then run them frequently, with different programatic configurations and system configurations and load. If tests ever fail, track down the failure. Don’t ignore a failure just because the tests pass on a subsequent run.
- Test spurious failures as candidate threading issues.
- Get your nonthreaded code working first.
- Make your threaded code pluggable.
- Make your threaded code tunable.
- Run with more threads than processors.
- Run on different platforms.
- Instrument your code to try and force failures.
Treat Spurious Failures as Candidate Threading Issues
- Do not ignore system failures as one-offs.
Get Your Nonthreaded Code Working First
- Do not try to chase down nonthreading bugs and threading bugs at the same time. Make sure your code works outside of threads.
Make Your Threaded Code Pluggable
- One thread, several threads, varied as it executes
- Threaded code interacts with something that can be both real or a test double.
- Execute with test doubles that run quickly, slowly, variable.
- Configure tests so they can run for a number of iterations.
Make Your Threaded Code Tunable
- Early on, find ways to time the performance of your system under different configurations.
Run with More Threads Than Processors
- The more frequently your tasks swap, the more likely you’ll encounter code that is missing a critical section or causes deadlock.
Run on Different Platforms
- This just reinforced the fact that different operating systems have different threading policies, each of which impacts the code’s execution.
- Multithreaded code behaves differently in different environments.
- You should run your tests in every potential deployment environment.
Instrument Your Code to Try and Force Failures
-
Wait()
,Sleep()
,Yield()
andPriority()
. Each of these methods can affect the order of execution, thereby increasing the odds of detecting a flaw.
Hand-Coded
- This inserted call to
Yield()
will change the execution path ways taken by the code and possibly cause the code to fail where it did not fail before.
Automated
- Though a bit simplistic, this could be a resonable option in lieu of a more sophisticated tool.
- The combination of well-written tests and jiggling can dramatically increase the chance finding errors.
- Use jiggling strategies to ferret out errors.
Chapter 14: Successive Refinement
-
Try-Catch
is very important.
Args Implementation
- Create function only for error message.
- Create Enum only for error code.
- The more errors are created, the more codes you should write.
Args: Thr Rough Draft
- It’s works. And it’s messy.
So I stopped
- Each argument type required some way to parse its schema element in order to select the
HashMap
for that type. - Each argument type needed to be parsed in the command-line strings and converted to its true type.
- each argument type needed a
getXXX
method so that it could be returned to the caller as its true type.
On Incrementalism
- One of the best ways to ruin a program is to make massive changes to its structure in the name of improvement.
- To avoid this, I use the discipline of Test-Driven-Development(TDD).
- Every change I make must keep the system working as it worked before.
- I could run these tests any time I wanted, and if they passed, I was confident that the system was working as I specified.
String Arguments
- Again, these changes were made one at a time and in such a way that the tests kept running, if not passing.
- When a test broke, I made sure to get it passing again before continuing with the next change.
- I deployed both
set
andget
, deleted the unused functions, and moved the variables.
Chapter 15: JUnit Internals
- We should make the names unambiguous.
- Ofen one refactorng leads to another that leads to the undoing of the first.
- Refactoring is an iterative process full of trial and error, inevitably converging on something that we feel is worthy of a professional.
Chapter 16: Refactoring SerialDate
- It’s generally a bad idea for base classes to know about their derivatives.
- To fix this, we should use the
Abstract Factory
pattern. - If something logical depends on the implementation, then something physical should too.
Chapter 17: Smells and Heuristics
Comments
C1: Inappropriate Information
- Comments should be reserved for technical notes about the code and design.
C2: Obsolete Comment
- If you find an obsolete comment, it is best to update it or get rid of it as quickly as possible.
C3: Redundant Comment
- Comments should say things that the code cannot say for itself.
C4: Poorly Written Comment
- A comment worth writing is worth writing well.
C5: Commented-Out Code
- It pollutes the modules that contain it and distracts the people who try to read it.
Environment
E1: Build Requires More than One Step
- Building a project should be a single trivial operation.
E2: Tests Requires More Than One Step
- Being able to run all the tests is so fundamental and so important that it should be quick, easy, and obvious to do.
Functions
F1: Too Many Arguments
- Functions should have a small number of arguments.
F2: Output Arguments
- Output arguments are counterintuitive.
F3: Flag Arguments
- They are confusing and should be eliminated.
F4: Dead Function
- Methods that are never called should be discarded.
General
G1: Multiple Languages in One Source File
- We should take pains to minimize both the number and extent of extra languages in our source files.
G2: Obvious Behavior Is Unimplemented
- When an obvious behavior is not implemented, readers and users of the code can no longer depend on their intuition about function names.
G3: Incorrect Behavior at the Boundaries
- Don’t rely on your intuition.
- Look for every boundary condition and write a test for it.
G4: Overridden Safeties
- Turning off failing tests and telling yourself you’ll get them to pass later is as bad as pretending your credit cards are free money.
G5: Duplication
- Find and eliminate duplication wherever you can.
G6: Code at Wrong Level of Abstraction
- Isolating abstractions is one of the hardest things that software developers do, and there is no quick fix when you get it wrong.
G7: Base Classes Depending on Their Derivatives
- When such components are modified, they can be redeployed without having to redeploy the base components. This means that the impact of a change is greatly lessened, and maintaining systems in the field is made much simpler.
G8: Too Much Information
- The fewer methods a class has, the better.
- The fewer variables a function knows about, the better.
- The fewer instance variables a class has, the better.
G9: Dead Code
- Give it a decent burial.
G10: Vertical Separation
- Local variables should be declared just above their first usage and should have a small vertical scope.
- Private functions should be defined just below their first usage.
G11: Inconsistency
- If you do simething a certain way, do all similar things in the same way.
- Simple consistency like this, when reliably applied, can make code much easier to read modify.
G12: Cluter
- Keep your source files clean, well organized, and free of clutter.
G13: Artifical Coupling
- In general an artificial coupling is a coupling between two modules that serves no direct purpose.
- It is a result of putting a variable, constant, or function in a temporarily convenient, though inappropriate, location.
G14: Feature Envy
- The methods of a class should be interested in the variables and functions of the class they belong to, and not the variables and functions of other classes.
G15: Selector Arguments
- Not only is the purpose of a selector argument difficult to remember, each selctor argument combines many functions into one.
G16: Obscured Intent
- It is worth taking the time to make the intent of our code visible to our reader.
G17: Misplaced Responsibility
- Code should be placed where a reader would naturally expect it to be.
G18: Inappropriate Static
- When in doubt, make the function nonstatic.
- If you really want a function to be static, make sure that there is no chance that you’ll want it to behave polymorphically.
G19: Use Explanatory Variables
- One of the more powerful ways to make a program readable is to break the calculations up into intermediate values that are held in variablles with meaningful names.
G20: Function Names Should Say What They Do
- If you have to look at the implementatio (or documentation) of the function to know what it does, then you should work to find a better name or rearrange the functionality so that it can be placed in functions with better names.
G21: Understand the Algorithm
- Programming is often an exploration.
- Befoore you consider yourself to be done with a function, make sure you understand how it works.
G22: Make Logical Dependencies Physical
G23: Prefer Polymorphism to If/Else or Switch/Case
G24: Follow Standard Conventions
G25: Replace Magic Numbers with Named Constants
G26: Be Precise
- Ambiguities and imprecision in code are either a result of disagreements or laziness.
G27: Structure over Convention
G28: Encapsulate Conditionals
G29: Avoid Negative Conditionals
- Negatives are just a bit harder to understand than positives.
G30: Functions Should Do One Thing
G31: Hidden Temporal Couplings
G32: Don’t Be Arbirary
G33: Encapsulate Boundary Conditions
G34: Functions Should Descend Only One Level of Abstraction
G35: Keep Configurable Data at High Levels
G36: Avoid Transitive Navigation
Java
J1: Avoid Long Import Lists by Using Wildcards
J2: Don’t Inherit Constants
J3: Constants versus Enums
Name
N1: Choose Descriptive Names
- Make sure the name is descriptive.
- You need to take the time to choose them wisely and keep them relevant.
N2: Choose Names at the Appropriate Level of Abstraction
- Don’t pick names that communicate implementation: choose names the reflect the level of abstraction of the clalss or function you are working in.
N3: Use Standard Nomenclature Where Possible
- The more you can use names that are overloaded with special meanings that are relevant to your project, the easier it will be for readers to know what your code is talking about.
N4: Unambiguous Names
N5: Use Long Names for Long Scopes
- You can use very short variable names for tiny scopes, but for big scopes you should use longer names.
N6: Avoid Encodings
- Keep your names free of Hungarian pollution.
N7: Names Should Describe Side-Effects
- Don’t hide side eggects with a name.
Test
T1: Insufficient Tests
- A test suite should test everything that could possibly break.
T2: Use a Coverage Tool!
- Coverage tools reports gaps in your testing strategy.
T3: Don’t Skip Trivial Tests
T4: An Ignored Test Is a Question about an Ambiguity
- Which you choose depends upon whether the ambiguity is about something that would compile or not.