Graduate Program KB

Clean Code

Chapter 17

Comments

  • Don't add inappropriate information (clutter), such as change histories, modified dates, metadata and so on
  • Comments become outdated quickly, best descriptions are through good naming and well structured code
  • Comments are redundant if a piece of code adequately describes itself
  • If a comment is necessary, ensure it's the best possible comment you can write (brief, grammar / punctuation, doesn't state the obvious)
  • Delete commented-out code, it becomes more irrelevant as development goes on

Environment

  • Building a project should requrie one step, whether it be checking out to the project directory or running a script to build it
  • All unit tests should be ran with only one command

Functions

  • Small number of arguments, preferably none. More than three is questionable
  • Output arguments are counterintuitive. If a function must change the state of something, change the state of the object it's called on
  • Flag arguments indicate a function performs more than one task, should be eliminated
  • Discard unused functions, it's simply unused code

General

  • The ideal source file should only contain one language
  • The Principle of Least Surprise suggests a function or class should implement behaviour that other programmers could reasonably expect
  • Ensure behaviour of code works for edge cases / boundaries
  • Overriding safeties are risky (turning off compiler warnings, hiding failing tests, etc.)
  • Remove duplication, enforce abstraction and code reusability
  • Important to create abstractions that separate high and low level concepts
  • Base classes should not depend on their derivatives in most cases
  • Well-defined modules have small interfaces that allow you to do a lot still. A well-defined interface has low coupling and few functions to depend upon
  • Delete dead code from the system including unreachable code, unused variables, functions, etc.
  • Variables and functions should be defined close to where they're used for association (vertical separation)
  • Your style of coding should be consistent throughout the system, relating back to the Principle of Least Surprise
  • Artifical coupling is a coupling between two modules that serves no direct purpose, this is lazy and should be avoided
  • The methods of a class should be interested in the variables and functions of the class they belong to, not any other classes
  • Remove selector arguments (similar to flags), essentially a dangling argument typically at the end of a function call which determines what logical path to take (boolean, enum, integer, etc.)
  • Make code as expressive as possible, revealing its intent to the reader
  • Place code where it's expected to be
  • In general, prefer nonstatic methods over static methods
  • Explanatory or descriptive variables are important, same applies with function names
  • Truly understand how your algorithm works, test your knowledge by refactoring after the tests pass
  • Make logical dependencies physical, the dependent module shouldn't make assumptions about the module it depends on
  • Use polymorphism instead of an if-else chain, deploy specific functionality to different derivatives
  • A development team should follow a common coding standard
  • Replace ambiguous numbers with named constants, it removes ambiguity for readers
  • Be very precise with how data is represented and the types you assign to it
  • Enforce design decisions with structure over convention
  • Encapsulate conditions from statements into separate functions, helps to describe the predicate and reduce clutter
  • Avoid negative conditions, it's just naturally harder to understand than positives
  • Functions should perform only one task
  • Temporal couplings may be necessary, but make it obvious and don't hide it
  • Change the structure of code to appear non-arbitrary
  • Boundary conditions can be difficult to keep track of, put all that processing in a single location rather than littering checks everywhere throughout the code
  • Functions should descend only one level of abstraction. Statements within the body should be on the same level of abstraction, which is a level below the operation described by the callee
  • Data that's configurable should be kept at a high level of abstraction
  • Ideally, a module shouldn't know much about its collaborators (Law of Demeter)

Java

  • Avoid long import lists by using wildcards (*)
  • Don't inherit constants
  • Use enums instead of constants wherever possible

Names

  • Choose descriptive names
  • Don't pick names communicating implementation, it should reflect the level of abstraction
  • Use standard nomenclature, utilising common terms within the industry or your development team will improve readability
  • Don't make names ambiguous
  • A big scope should use a longer name, and vice versa
  • Avoid names encoded with type or scope information. Unnecesary to have prefixes
  • Names should describe everything that a function, class or variable does, including side effects

Tests

  • A test suite should test everything that could possibly break
  • Use coverage tools to report gaps in your testing strategy
  • Don't skip trivial tests, simple to write and provide documentary value
  • If a test is ambiguous, can use an @Ignore or skip (depends on framework) and come back later
  • Test boundary conditions thoroughly
  • Upon finding a bug, test that function exhaustively
  • Sometimes you can diagnose problems by finding a common pattern in which test cases fail, the same can be applied by looking at passing tests
  • Tests should be fast, use mocks for time-based tests, etc.