You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
Complexity is incremental: you have to sweat the small stuff (see p. 11).
Working code isn’t enough (see p. 14).
Make continual small investments to improve system design (see p. 15).
Modules should be deep (see p. 22)
Interfaces should be designed to make the most common usage as simple as possible (see p. 27).
It’s more important for a module to have a simple interface than a simple implementation (see pp. 55, 71).
General-purpose modules are deeper (see p. 39).
Separate general-purpose and special-purpose code (see p. 62).
Different layers should have different abstractions (see p. 45).
Pull complexity downward (see p. 55).
Define errors (and special cases) out of existence (see p. 79).
Design it twice (see p. 91).
Comments should describe things that are not obvious from the code (see p. 101).
Software should be designed for ease of reading, not ease of writing (see p. 149).
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).
nice good summary. Im glad you liked the read