- Clean Code
- Meaningful Names
- Functions
- Comments
- Formatting
- Objects and Data Structures
- Error Handling
- Boundaries
- Unit Tests
Clean Code
- There is no such thing as "no code", as code represents the details of requirements. At some level these details must be specified, and specifying requirements in detail is itself programming and such a specification is code.
- Having good code matters. Code messiness, if not cleaned up, compounds over time until it is unmanageable.
- Remember LaBlanc's law: Later equals never.
- Messy code means every addition or modification a system requires is non-trivial and requires understanding of every twist and knot in the system.
- Additions to code may break other features.
- Eventually it's impossible to clean up no matter how many staff are assigned to it. Redesigns become extremely costly and involve lots of work to catch up to the original design's capabilities.
- It is unprofessional for programmers to bend to the will of managers who don't understand the risks of making messes.
- The only way to make a deadline is to keep code as clean as possible at all times. Mess will just slow you down.
- Writing clean code requires the use of techniques applied through an acquired sense of code cleanliness.
- What is Clean Code™?
- Pleasing to read. Simple and direct, like well-written prose. Looks like it was written by someone who cares.
- Nothing obvious you could do to make the code better. Each routine you read turns out to be about what you expected.
- Efficient so others aren't tempted to attempt messy optimizations.
- Straightforward logic and lines of control. Full of crisp abstractions.
- Bugs will be obvious.
- Errors will be handled via an articulated strategy.
- Each module exposes a single-minded attitude that remains entirely undistracted by surrounding details. Never obscures the designer's intent.
- Has meaningful names.
- Contains no duplication.
- Runs unit and acceptance tests.
- Has minimal dependencies, which are explicitly defined with clear and minimal API.
- Makes the language look like it was made for the problem at hand.
- There is no one 'right' school of thought about how to write clean code.
- Developers are responsible for communicating well with their readers
- The majority of time writing code is spent reading other code
- Making your code easier to read will make new code easier to write
- The Boy Scout Rule: "Leave the campground cleaner than you found it".
- If everyone did this for code, it simply would not rot.
Meaningful Names
- Use Intention-Revealing Names
- Choosing good names takes time but saves more than it takes
- The name of something should tell you why it exists, what it does and how it is used. If it requires a comment, it does not reveal its intent
- Avoid Disinformation
- Avoid leaving false clues that obscure the meaning of code
- Don't refer to a group of data structures as a
somethingList
unless it's actually a List, as it may lead to false conclusions - Beware of using names which vary in small ways
- Make Meaningful Distinctions
- If the compiler insists that names must be different, then they should also mean something different
- Number series naming is the opposite of intentional naming
- Avoid noise words like:
- a, an, the
- Info, Data, Object, Amount
- Use Pronounceable Names
- Avoid made-up words like acronyms
- Use Searchable Names
- Numeric constants are not easy to locate and should have meaningful names
- Single-letter names should only be used as local variables inside short methods
- Avoid Encodings
- Avoid Hungarian Notation (e.g.
lAccountNum
, with "l" implying a long integer) - Avoid Member Prefixes (e.g. m_variable)
- Avoid adorning interfaces and implementations, e.g.
ShapeFactoryImp
- Avoid Hungarian Notation (e.g.
- Avoid Mental Mapping
- Readers shouldn't have to mentally translate your names into other names they already know
- Another reason why you should avoid using single-letter names outside of loop counters
- Professional programmers understand that clarity is king
- Class Names
- Classes and objects should have noun or noun-phrase names, e.g.
Customer
,WikiPage
,Account
,AddressParser
- Avoid words like
Manager
,Processor
,Data
orInfo
in class names - Class names should not be a verb
- Classes and objects should have noun or noun-phrase names, e.g.
- Method Names
- Methods should have verb or verb-phrase names like
postPayment
,deletePage
orsave
- Accessors, mutators and predicates should be named for their value and prefixed with
get
,set
,is
- When constructors are overloaded, use static factory methods with names that describe the arguments instead, e.g.
variable = Complex.FromRealNumber(x);
is better thanvariable = new Complex(23.0)
- Consider enforcing factory use by making the corresponding constructors private
- Methods should have verb or verb-phrase names like
- Don't Be Cute
- Avoid names that are colloquial, slang, jokes. Choose clarity over entertainment value.
- Pick One Word per Concept
- What's the difference between
fetch
,retrieve
andget
methods?? - What's the difference between a
controller
,manager
anddriver
?? - Pick one name for a concept and stay consistent within your project/codebase.
- What's the difference between
- Don't Pun
- Avoid using the same word for two purposes
- Don't use 'add' for adding items to a list if you use 'add' to mean something else elsewhere
- Use Solution Domain Names
- Use names that would be meaningful to programmers familiar with the pattern you're using
- Use Problem Domain Names
- When there are no solution domain names for what you're doing, use the name from the problem domain
- Add Meaningful Context
- If names would not be meaningful by themselves and are only meaningful in context:
- Try to enclose them in well-named classes, functions or namespaces
- Else, try prefixing the name as a last resort, e.g.
addrFirstName
,addrLastName
,addrStreet
,addrNumber
,addrCity
,addrState
,addrZipCode
- If names would not be meaningful by themselves and are only meaningful in context:
- Don't Add Gratuitous Context
- Add no more context to a name than is necessary
Functions
- Functions should be as small as possible
- Indent level of functions should not be greater than 1 or 2. Extract nested code out.
- Functions should only do one thing and do it well.
- One level of abstraction per function.
- Stepdown Rule: Each function should be a "step-down" in abstraction from its caller.
- Switch statements inherently do more than one thing. They should be buried in an inheritance relationship so the rest of the system cannot see them. They should only appear once per case analysis and should create polymorphous objects.
- Don't be afraid of long, descriptive names
- Fewer function arguments are better.
- Niladic is better than monadic is better than dyadic, etc.
- Avoid flag arguments, as they inherently mean a function does more than one thing
- When a function seems to need 2 or more arguments, consider requiring the use of wrapper object to pass in arguments
- A function should have no side effects, as side effects violate the single-responsibility principle and create temporal coupling.
- Functions should either do something or answer something
- Prefer throwing exceptions to returning error codes
- Extract try-catch blocks to their own function that calls the exception producing function.
- Error handling is a single responsibility
- Don't repeat yourself. Duplication is the root of all evil.
- To write good functions, treat them as any other form of writing, i.e. start with a draft and iterate on it.
Comments
- Inaccurate comments are worse than no comments
- Energy directed toward maintaining comments would be better spent maintaining code
- The proper use of comments is compensating for failure to express ourselves in code.
- Clean your code, don't comment.
- Good comments
- Legal (e.g. licensing)
- Informative
- Explanation of intent
- Clarification, especially if you have to interface with code you cannot rewrite.
- Warning of consequences
- Amplification of importance
- TODOs
- "JavaDoc" style comments in public APIs
- Bad comments
- "Mumbling" - Adding comments just because you think you should.
- Redundant comments
- Misleading comments
- Mandated comments
- Journal comments
- "Noise" comments
- Position markers
- Attributions/Bylines
- Commented-out code
- Non-local information
- Infodumps (e.g. details of an RFC spec)
- Inobvious connections
- JavaDocs in non-public code
Formatting
- Code should be nicely formatted according to a set of simple rules agreed upon by your team that are consistently applied across a project.
- The purpose of formatting is to enhance readability and through that the setting of precedents that enhance the maintainability and extensibility of future iterations of code you're writing.
- Readers should be able to trust that formatting gestures seen in one source file will mean the same thing in others.
Vertical Formatting
- Prefer smaller files, as they are usually easier to understand than large files.
- The Newspaper Metaphor: A source file should be like a newspaper article:
- Name should be simple, explanatory, and should be sufficient to tell readers whether they are in the right module or not
- The topmost parts of the source file should provide the high-level concepts and algorithms
- Detail should increase as you move downward
- Should be composed of many sections, where the size of each varies but is only as long as is necessary to tell the story it needs to tell
- There should be vertical openness between concepts, where separate concepts are separated by blank lines.
- Vertical density should be used to imply close association between concepts.
- Vertical distance between concepts should be used to show how important each is to understand the other.
- Variables should be declared as close to their usage as possible
- Instance variables should be declared at the top of a Class as they are needed to understand the methods that use them
- Dependent functions should be vertically close, and if possible the caller should be above the callee.
- As conceptual affinity between snippets of code increases, vertical distance between them should lessen. Conceptual affinity might be caused by snippets having a dependency relationship, performing similar operations or having a common naming scheme.
Horizontal Formatting
- Programmers prefer shorter lines. One rule of thumb is that you should never have to scroll to the right but considering monitor size can vary wildly nowadays, common limits include 80, 100 and 120 characters per line.
- Horizontal whitespace should be used to associate things that are strongly related and disassociate things that are weakly related, eg.
// Accentuate assignment operators by surrounding them with whitespace
// Don't put spaces between function names and opening parenthesis as functions and their arguments are closely related.
double determinant = determinant(a, b, c);
// Whitespace can be used to accentuate the precedence of operators
return (-b + Math.sqrt(determinant)) / (2*a);
- Horizontal alignment (eg. declaring your Java variables with tab separations between the access modifiers, type and variable name) is not useful. Alignment emphasizes the wrong things and leads the eye away from the code's true intent.
- If you have long lists of declaration that look like they need to be aligned, the problem is the length of the lists
- Indentation should be used to illustrate scope hierarchy, even for smaller code blocks and 'dummy scopes' with no body.
Objects and Data Structures
- A class shouldn't expose its data or details about its data. It should expose abstract interfaces that allow users to manipulate the essence of the data.
- A class's interface should reveal the designer's intention for the class's use.
- Objects and data structures are virtual opposites:
- Objects hide their data and expose functions that operate on that data. When using objects, it's easy to add new data types without needing to add new functions.
- Data structures expose their data and have no meaningful functions. When using data structures, it's easy to add new functions without needing to add new data types.
- Law of Demeter: An module should never need to know about the innards of the objects it manipulates. Example:
String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
// Dirty! This method needs to know about the interface of an internal object of an internal object of ctxt. If any of these classes change their interfaces this would need to be re-written.
- Object methods should do something, not just access/mutate internals.
- The line of code given above was used in a module to retrieve a path to create a file stream. If this is an intended use case, you could consider cleaning this up by adding a method that does this behaviour, e.g.
ctxt.createScratchFileStream(classFileName)
- The line of code given above was used in a module to retrieve a path to create a file stream. If this is an intended use case, you could consider cleaning this up by adding a method that does this behaviour, e.g.
Error Handling
- Use exceptions over return codes (eg. returning -1 on error)
- If you are using TDD to build a method that will encounter exceptions, test exception handling behaviour and write your
try-catch-finally
first, before developing the method further. - Use unchecked exceptions (ie. runtime exceptions, not exceptions)
- Exceptions should provide context with informative error messages. If you are logging, pass enough information to be able to log the error in your
catch
- Special Case Pattern - Create a class or configure an object so that it handles a special case for you so the client doesn't have to deal with exceptions.
- Don't return null and don't pass null! Clutters code with null checks
Boundaries
- Boundary - Points of interaction between your code and foreign code dependencies.
- Boundaries should be encapsulated (eg. using the Adapter pattern) so only code inside the encapsulating class needs to change if the dependency's interface changes.
- Learning Tests - Creating a test suite for a dependency to ensure you understand how its interface works and verify that behaviour has not changed when using new releases. Tests should include all boundaries you intend to use in your application.
- Code at the boundaries needs clear separation and tests that define expectation. Avoid letting too much of your code know about particulars of third-party code.
Unit Tests
-
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. Not compiling counts as failing.
- You may not write more production code than is sufficient to pass the currently failing test
-
Test code is just as important as production code. It requires thought, design and care and must be kept as clean as production code.
-
Unit tests keep code flexible, maintainable and reusable, as tests enable change.
- If you have tests, you don't fear making changes as you have guarantees that you haven't caused a regression.
- The dirtier your tests, the dirtier your code becomes.
-
Readable tests are clean tests
- Extract duplicate code so that the purpose of each test becomes more clear.
- Make tests simple, succinct and expressive.
- No need to make it as efficient as production code.
-
Build-Operate-Check Pattern: Split your test into 3 parts:
- Build up test data
- Operate on the test data
- Check that the operation yielded the expected results
-
Use a domain-specific testing language
-
Minimize the number of asserts in a test (keep it to one assert if you can)
-
Keep to a single concept in each test
-
F.I.R.S.T. rules for Clean Tests:
- Fast - Tests should run quickly. If tests are slow, you won't want to run them frequently.
- Independent - Tests should not depend on each other
- Repeatable - Tests should be repeatable in any environment
- Self-Validating - Tests should have a boolean output (PASS or FAIL)
- Timely - Write tests just before writing the code that makes them pass. If you write tests after the production code, you may find it hard to test.