Skip to content

Instantly share code, notes, and snippets.

@davegonzalez
Created July 7, 2018 04:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davegonzalez/624e83818c686fc9fe58fcd1a2f56d8a to your computer and use it in GitHub Desktop.
Save davegonzalez/624e83818c686fc9fe58fcd1a2f56d8a to your computer and use it in GitHub Desktop.

Selections from Clean Code: A Handbook of Agile Software Craftmanship

Chapter 2: Meaniningful Names

  • Avoid disinformation
  • Make meaninful distinctions
    • Imagine that you have a Product class. If you have another called ProductInfo or ProductData, you have made the names different without making them mean anything different. Info and Data are indistinct noise words as well.
    • Noise words are redundant. How is NameString better than Name? Would a Name ever be a floating point number? Doing so would cause disinformation.
    • Imagine finding one class named Customer and another named CustomerObject - what should you understand as the distinction?
  • Class names
    • Classes and objects should have noun or noun phrase names like Customer, Wikipage, Account, and AddressParser. Avoid words like Manager, Processor, Data, or Info in the name of a class.
  • Method names
    • Methods should have verb or verb phrase names like postPayment, deletePage, or save. Accessors, mutators, and predicates should be named for their value and prefixed with get, set, and is according
  • Pick one word per concept
    • Example: it's confusing to have fetch, get, and retrieve as equivalent methods of different classes

Chapter 3: Functions

  • 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.
  • Do one thing
    • Functions should do one thing. They should do it well. They should do it only.
    • One way to know if a function is doing more than "one thing" is if you can extract another function from it with a name that is not merely a restatement of its implementation.
  • Use descriptive names
    • Choosing descriptive names will clarify the design of the module in your mind and help you improve it. It is not at all uncommon that hunting for a good name results in a favorable restructuring of your code.
  • Function arguments
    • Functions with many arguments are hard from a testing point of view. Imagine a function with multiple arguments and the difficulty of writing all the test cases to ensure that all the various combinations of arguments work properly.
    • Note: The book suggests that no arguments are best - this is ideal in the case of OOP. A single argument is best in FP.
  • Flag arguments
    • Flag arguments are not ideal. Passing a boolean into a function complicates the signature of the method.
  • Have no side effects
    • A side effect is when your function promises to do one thing, but it also does other hidden things.
  • Error handling is one thing
    • Functions should do one thing - error handling is one thing. A function that handles an error should do nothing else.
  • Misc.
    • Functions are the verbs of the language, classes are the nouns.
    • The art of programming has been the art of language design.
    • Master programmers think of systems as stories to be told rather than programs to be written.

Chapter 4: Comments

  • Comments do not make up for bad code
    • 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 write comments when we should be refactoring our code.
  • Explain yourself in code
    • Use your code as a vehicle for explanation, rather than comments.
    • Which would you rather see? if (employee.flags && HOURLY_FLAG) && employee.age > 65 or if employee.isEligibleForFullBenefits()
  • Explanation of intent
    • Sometimes a comment goes beyond just useful information about the implementation; it provides the intent behind the decision.
  • Redundant comments
    • When a comment simply describes the code itself, it is useless, i.e. i++ // increment i

Chapter 7: Error handling

  • Write your Try-Catch statement first
    • Starting with your Try-Catch statement helps you define what the user of the code should expect, no matter what goes wrong with the code executed in the try.
  • Provide context with exceptions
    • Create informative error messages and pass them along with your exceptions
  • Don't return null
    • Returning null creates extra work for ourselves as we have continually make sure we check against it
    • Instead return a SPECIAL CASE object that returns a default behavior or wrap it in a method that throws an exception
    • https://martinfowler.com/eaaCatalog/specialCase.html
  • Don't pass null
    • Exception is when an API is expecting you to pass null

Chapter 9: Unit tests

  • The three laws of TDD
      1. You may not write production code until you've written a failing test
      1. You may not write more of a unit test than is sufficient to fail, and not compiling is failing
      1. You may not write more production code than is sufficient to pass the currently failing test
    • Addendum: ask yourself; What is the least amount of code I can write to pass this test?
  • Dirty tests
    • Test code is just as important as production code
    • Keep tests clean and up to date with evolving production code
  • Tests enable the -ilities
    • Tests keep code flexible, maintainable, and reusable
    • If you have tests, you do not fear making changes to the code!

Chapter 10: Classes

  • Classes should be small!
    • If we cannot derive a concise name for the class, then it's likely too large
    • The more ambiguous the class name, the more likely it has too many responsibilites
    • Processor, Manager, or Super often hint at unfortunate aggregation of responsibilites
  • Single Responsibility Principle (SRP)
    • SRP states that a class or module should have one, and only one, reason to change. Classes should have one responsibility.

The problem is too many of us think we are done once the program works. We fail to switch to the other concern of organization and cleanliness. We move on to the next problem rather than going back and breaking the overstuffed classes into decoupled units with single responsibilities.

At the same time, many developers fear that a large number of small, single-purpose classes makes it more difficult to understand the bigger picture. They are concerned that they must navigate from class to class in order to figure out how a large piece of the work gets accomplished.

However, a system with many small classes has no more moving parts than a system with a few large classes. Do you want your tools organized into toolboxes with many small drawers each containing well-defined and well-labeled components? Or do you want a few drawers that you just toss everything into?

Chapter 11: Systems

  • Scaling up
    • It is a myth that we can get systems "right the first time". Instead, we should implement only today's stories, then refactor and expand the system to implement new stories tomorrow. This is the essence of iterative and incremental agility.
    • Software systems are unique compared to physical systems. Their architectures can grow incrementally, if we maintain the proper separation of concerns.
  • Test drive the system architecture
    • It is not necessary to do a Big Design Up Front (BDUF) - it is harmful because it inhibits adapting to change, due to the psychological resistance to discarding prior effort and because of the way architecture choices influence subsequent thinking about the design.
    • We can start a software project with a naively simple but nicely decoupled architecture, adding more infrastructure as we scale up
    • We do not go in rudderless, we have some expecations of the general scope, goals, and schedule for the project as well as the general structure of the resulting system
  • Misc.
    • Whether you are designing systems or individual modules, never forget to use the simplest thing that can possibly work

Chapter 12: Emergence

  • Expressive
    • You can express yourself by choosing good names. We want to be able to hear a class or function name and not be surprised when we discover its responsibilities.
    • You can also express yourself by keeping your functions and classes small. Small classes and functions are usually easy to name, easy to write, and easy to understand.
    • The most important way to be expressive is to try. Care is a precious resource.

Chapter 13: Successive Refinement

It is not enough for code to work. Code that works is often badly broken. Programmers who satisfy themselves with merely working code are behaving unprofessionally. They may fear that they don't have time to improve the structure and design of their code, but I disagree. Nothing has a more profound and long-term degrading effect upon a development project than bad code. Bad schedules can be redone, bad requirements can be redefined, bad team dynamics can be repaired, but bad code rots and becomes a weight that drags the team down. Cleaning up bad code is expensive.

Chapter 17: Smells and Heuristics

  • Replace magic numbers with constants
    • i.e. 86,400 should be hidden behind a SECONDS_PER_DAY constant. If you're printing 55 lines per page, hide the number behind a LINES_PER_PAGE constant.
  • Encapsulate conditionals
    • Extract functions that explain the intent of the the conditional
    • i.e. if shouldBeDeleted(timer) vs if timer.hasExpired() && !timer.isRecurrent()
  • Note: These notes are light because this chapter was mostly a recap of everything in the other chapters.
@YanTheFawn
Copy link

Nice, thanks!

@erikmoldovan
Copy link

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment