Skip to content

Instantly share code, notes, and snippets.

@thor314
Last active July 17, 2024 17:31
Show Gist options
  • Save thor314/3326fc97eac0e3c34ee8f8d7b67e3a21 to your computer and use it in GitHub Desktop.
Save thor314/3326fc97eac0e3c34ee8f8d7b67e3a21 to your computer and use it in GitHub Desktop.
book-A philosophy of software design.md

book-A philosophy of software design

takeaways

  • two general approaches to fight complexity
    • make code simpler and more obvious
    • encapsulate the complexity with modular design
  • goals
    • how recognize complexity
    • techniques to minimize complexity
    • book designed to be used with code reviews
  • book pitfall - object oriented design tips only sometimes generalize, this book probably undersells functional programming techniques
  • complexity is the child of dependencies and obscurity, when important information is not obvious
  • how complexity manifests
    • change amplification - what should be a simple code change requires changes in many places
    • cognitive load - how much a developer needs to know to complete a task
    • unknown unknowns - not obvious which pieces of code must be modified to complete task, big bad,
  • tactical short sighted programming creates spaghetti code, hard to fix, hard to attract or retain good engineers to work on spaghetti
  • Modules should be obvious, then modules should be deep
  • the best modules have simple interfaces and abstracted implementations
  • abstractions - each module provides an interface as an abstraction from unimportant details. Unclean abstraction and the details bubble up.
  • deep modules with clean-as-possible abstractions are ideal to strive for, to hide complexity
  • 🚩 shallow modules that don't abstract much add as much complexity as is removed
  • 🚩 Information leakage occurs when the same knowledge is used in multiple places, such as two different classes that both understand the format of a particular type of file.
  • interfaces should be designed to make the common case as simple as possible
  • 🚩 pass through methods indicate that there is not a clean division of responsibility between classes. Pass through methods contribute no functionality.
  • it's more important for a module to have a simple interface than a simple implementation
  • bring code together
    • when information is shared
    • to simplify the interface
    • to DRY
  • separate general purpose and special purpose code
  • don't split methods unnecessarily. each method should do one thing and do it completely
  • 🚩 two pieces of code are physically separated, but each can only be understood by looking at the other
  • Define your types so that errors can't exist, like a sword that can't be held at the wrong end.
  • considering more options up front results in better design -- even better to think with more than one brain -- similar advice: think with brain before thinking with fingers
  • comments good, actually -- if i must read the implementation to understand, there is no abstraction
  • comments should describe things that aren’t obvious from the code -- Developers should be able to understand the abstraction provided by a module without reading any code other than its externally visible declarations. omments augment the code by providing information at a different (generally higher) level of detail.
  • 🚩 comments should not repeat the code.
  • 🚩 if the documentation must discuss the implementation, then the abstraction is shallow.
  • bad names cause bugs. good names are precise and self documenting. If it's hard to choose a name, the function or variable may have too much purpose, better to break it down.

1-5

1 intro

  • two general approaches to fight complexity which makes bugs
    • make code simpler and more obvious
    • encapsulate the complexity with modular design
  • waterfall bad, agile good, focus on subset of overall functionality and incremental development
  • goals
    • how recognize complexity
    • techniques to minimize complexity, e.g.
      • classes should be deep
      • define errors out of existence
    • book designed to be used with code reviews
  • pitfall - object oriented design tips only sometimes generalize, this book probably undersells functional programming techniques

2 nature of complexity

  • Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.
  • eliminating unnecessary complexity best, isolating complexity in layers to contain the demons within second best
  • how complexity manifests
    • change amplification - what should be a simple code change requires changes in many places
    • cognitive load - how much a developer needs to know to complete a task
    • unknown unknowns - not obvious which pieces of code must be modified to complete task, big bad,
  • complexity is the child of dependencies and obscurity, when important information is not obvious

3 working code isn't enough

  • two programming approaches
    • tactical programming - get thing to work now - short vision - tend to accrue complexity - harder to fix complexity than to program with complexity in mind up front - talented tactical programmer may write much code to get problem solved fast, but hard to work with or understand work
    • strategic programmer - working code is not enough - developer must anticipate and facilitate extensions to their code - anticipate with documentation, working toward simplest struct designs and APIs - make continual small improvements
  • if the codebase is spaghetti to begin with, much harder to attract good engineers to work on your spaghetti

4 Modules should be deep

  • first chapter I have difference of opinion about. Modules should be obvious, before they should be deep.
  • the best modules have simple interfaces and abstracted implementations
    • thought experiment: how achieve in Rust? Traits for public methods? Maybe sometimes. Separate high level callers from low level implementation.
  • abstractions - each module provides an interface as an abstraction from unimportant details. Unclean abstraction and the details bubble up.
  • deep modules with clean-as-possible abstractions are ideal to strive for, to hide complexity
  • 🚩 shallow modules that don't abstract much add as much complexity as is removed
    • authors flag small classes as shallow modules that do not help with managing complexity
  • interfaces should be designed to make the common case as simple as possible

5 information hiding and leakage

  • info hiding reduces complexity by
    • simplify the module interfaces reduces cognitive load
    • more modular, easier to evolve the system
  • 🚩 Information leakage occurs when the same knowledge is used in multiple places, such as two different classes that both understand the format of a particular type of file.
  • didn't find this chapter very useful

6-10 largely skippable

6 general purpose modules are deeper

  • restates premise of ch 4

7 different layer, different abstraction

  • 🚩 pass through methods indicate that there is not a clean division of responsibility between classes. Pass through methods contribute no functionality.

8 pull complexity down

  • it's more important for a module to have a simple interface than a simple implementation

9 better together or apart?

  • bring code together
    • when information is shared
    • to simplify the interface
    • to DRY
  • separate general purpose and special purpose code
  • don't split methods unnecessarily. each method should do one thing and do it completely
  • 🚩 two pieces of code are physically separated, but each can only be understood by looking at the other

10 define errors out of existence

  • Define your types so that errors can't exist, like a sword that can't be held at the wrong end.

11-15

11 design it twice

  • considering more options up front results in better design -- even better to think with more than one brain -- similar advice: think with brain before thinking with fingers

12 why write comments

  • comments good, actually -- if i must read the implementation to understand, there is no abstraction

13 comments should describe things that aren't obvious

  • comments should describe things that aren’t obvious from the code -- Developers should be able to understand the abstraction provided by a module without reading any code other than its externally visible declarations. omments augment the code by providing information at a different (generally higher) level of detail.
  • most comments will comment on one of the following:
    • interface - describe the abstractions
    • implementation details or side effects
    • data structure member
    • cross module - describe dependencies that pass between module boundaries
    • module design logic - authors didn't mention this but it felt important
  • 🚩 comments should not repeat the code.
  • 🚩 if the documentation must discuss the implementation, then the abstraction is shallow.

14 choosing names

  • bad names cause bugs. good names are precise and self documenting. If it's hard to choose a name, the function or variable may have too much purpose, better to break it down.

15 write comments first as part of the design process

  • this seems controversial, too much potential for premature calcification of design that must still change

16-20

16 modify existing code - shallow

  • If you’re not making the design better, you are probably making it worse
  • comments belong in the code, not the commit

17 consistency - shallow

  • do things in consistent ways

18 code should be obvious - shallow

19 software trends - skipped

20 on designing for performance - skipped

design principles summary

  1. Complexity is incremental: you have to sweat the small stuff (see p. 11).
  2. Working code isn’t enough (see p. 14).
  3. Make continual small investments to improve system design (see p. 15).
  4. Modules should be deep (see p. 22)
  5. Interfaces should be designed to make the most common usage as simple as possible (see p. 27).
  6. It’s more important for a module to have a simple interface than a simple implementation (see pp. 55, 71).
  7. General-purpose modules are deeper (see p. 39).
  8. Separate general-purpose and special-purpose code (see p. 62).
  9. Different layers should have different abstractions (see p. 45).
  10. Pull complexity downward (see p. 55).
  11. Define errors (and special cases) out of existence (see p. 79).
  12. Design it twice (see p. 91).
  13. Comments should describe things that are not obvious from the code (see p. 101).
  14. Software should be designed for ease of reading, not ease of writing (see p. 149).
  15. The increments of software development should be abstractions, not features (see p. 154).

🚩 red flags summary 🚩

  • Shallow Module: the interface for a class or method isn’t much simpler than its implementation (see pp. 25, 110).
  • Information Leakage: a design decision is reflected in multiple modules (see p. 31).
  • Temporal Decomposition: the code structure is based on the order in which operations are executed, not on information hiding (see p. 32).
  • Overexposure: An API forces callers to be aware of rarely used features in order to use commonly used features (see p. 36).
  • Pass-Through Method: a method does almost nothing except pass its arguments to another method with a similar signature (see p. 46).
  • Repetition: a nontrivial piece of code is repeated over and over (see p. 62).
  • Special-General Mixture: special-purpose code is not cleanly separated from general purpose code (see p. 65).
  • Conjoined Methods: two methods have so many dependencies that its hard to understand the implementation of one without understanding the implementation of the other (see p. 72).
  • Comment Repeats Code: all of the information in a comment is immediately obvious from the code next to the comment (see p. 104).
  • Implementation Documentation Contaminates Interface: an interface comment describes implementation details not needed by users of the thing being documented (see p. 114).
  • Vague Name: the name of a variable or method is so imprecise that it doesn’t convey much useful information (see p. 123).
  • Hard to Pick Name: it is difficult to come up with a precise and intuitive name for an entity (see p. 125).
  • Hard to Describe: in order to be complete, the documentation for a variable or method must be long. (see p. 131).
  • Nonobvious Code: the behavior or meaning of a piece of code cannot be understood easily. (see p. 148).
@0xJepsen
Copy link

nice good summary. Im glad you liked the read

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