A Philosophy of Software Design

  1. Introduction
    • If you can visualize a system, you can probably implement it
    • Means greatest limitation is the  ability to understand the system we are creating
    • Over time, complexity accumulates, leading to bugs which slows development
    • two general approaches to fight complexity
      • making code simpler and more obvious
      • encapsulate complexity
    • software design continual process, waterfall rarely works, initial design will have many problems
    • design never done, should always look for opportunities to improve
    • goal 1: describe the nature of software complexity
    • goal 2: present techniques to minimize complexity
    • try to use book in conjunction with code reviews
    • easier to see design problems in others’ code
    • consider alternative designs, don’t give up easily
  2. The Nature of Complexity
    • complexity
      • anything related to the structure of a software system that makes it hard to understand and modify
      • overall complexity determined by the fraction of time developers spend working on that part
      • isolating complexity in a place where it will never be seen is almost as good as eliminating it
      • complexity more apparent to readers than writers
    • system of complexity
      • change amplification: simple changes requires code modifications in many different places
      • cognitive load: how much a developer needs to know in order to complete a task
        • sometimes more lines of code simpler because reduces cognitive load
      • unknown unknowns: not obvious which pieces of code must be modified to complete a task
      • an important goal in good design is for the system to be obvious
    • causes of complexity
      • 2 things: dependencies and obscurity
      • when code cannot be modified in isolation
      • try to reduce the number of dependencies
      • obscurity: important information not obvious
      • inconsistency or inadequate documentation is a major contributor
    • complexity is incremental
      • accumulates in lots of small chunks
      • incremental nature makes hard to control
      • adopt a “zero tolerance” philosophy
    • conclusion
      • complexity results in more time for modifications
      • developers spend more time acquiring enough info to safely make changes
  3. Working Code Isn’t Enough
    • Intro
      • many organizations promote a quick tactical approach
      • for good design, want to take a more strategic approach
      • cheaper in the long run
    • tactical programming
      • the main focus is getting it working quickly
      • makes it nearly impossible to produce a good design
      • how systems incrementally become complicated
      • the organization may view tactical tornadoes as heroes which require other engineers to clean up (making them look slower)
    • strategic programming
      • working isn’t enough
      • most important is long-term structure of system
      • most important job is to facilitate future extensions
      • requires an investment mindset
      • speeds you up in the long-term
      • rather than implementing the first design, try to think of a couple of alternatives and pick the cleanest one
      • inevitably will make mistakes, when you discover, take a little extra time to fix it
    • how much to invest?
      • the ideal design emerges from bits and pieces
      • make lots of small investments on a continual basis
      • 10-20% of dev time
      • allows quicker development in the future
    • startup and investment
      • once a code base turns to spaghetti, almost impossible to fix
      • an important factor for success is also quality of engineers
      • best engineers care deeply about good design
      • the company can succeed with both approaches, but usually more fun to work at a company that cares about design
    • conclusion
      • good design doesn’t come for free
      • need to invest in continually
      • if you keep delaying improvements, easy for delays to become permanent
  4. Modules Should Be Deep
    • Intro
      • one of the most important techniques is to the design system so developers only need to face a small fraction of the overall complexity at any given time
    • Modular Design
      • ideally independent, but in real life, must interact and call each other’s methods
      • must know something about the other
      • the goal of modular design is to minimize dependencies
      • 2 parts: interface and implementation
      • the interface is everything a developer working in a different module must know in order to use it
      • the interface describes the “what” not the “how”
      • the implementation carries out the promise
      • best modules have interfaces that are much simpler than the implementation
      • simple interface imposes less complexity to the system
      • if the module is modified without changing the interface, it doesn’t affect other users
    • What’s in an interface
      • formal and informal info
      • formal is code signature
      • informal can’t be specified in code and can only be described in the comments
    • abstractions
      • a simplified view of an entity which omits unimportant details
      • interface provides a simplified view of functionality
      • abstractions go wrong when 1) include details that are not important (more complex than they need to be) and 2) omit details that are important (obscurity)
      • the key is knowing what is important
    • deep modules
      • best modules provide powerful functionality but simple interfaces
      • cost/benefit
      • benefit: functionality
      • cost: in terms of system complexity is the added interface
    • shallow modules
      • the complexity of the interface nearly as great as the implementation
      • sometimes unavoidable
      • doesn’t help manage complexity
    • Class-itis
      • conventional wisdom today is classes should be small not deep
      • class may be simple, but produces a lot of complexity from accumulated interfaces
    • Java and Unix I/O
      • good to make the common case as simple as possible
      • effective complexity is the complexity of commonly used features
    • conclusion
      • make modules deep with simple interfaces
  5. Information Hiding (and Leakage)
    • Intro
      • most important for achieving deep modules
      • each module should encapsulate a few pieces of knowledge
      • embedded in implementation but not visible in the interface
      • info such as data structures, algorithms, low-level such as page size
      • 1) this simplifies the interface
      • 2) makes the system easier to evolve
      • if info is hidden, no dependencies
      • best is when completely hidden, but can still be visible if partially hidden e.g. particular feature can be accessed with another method
    • Information Leakage
      • when design decision reflected in multiple modules
      • change requires changes to multiple modules
      • can leak even if not in interface e.g. both depend on certain file format (not obvious)
      • information leakage is one of the most important red flags in software design
      • if affected classes are small, maybe should merge? or can pull out to a new class
    • temporal decomposition
      • when the order of operations matter
      • e.g. having a separate class to read and another class to write a file
      • when designing a module, focus on the knowledge that’s needed to perform each task, not order in which tasks occur
    • HTTP example
      • if classes share a lot of knowledge better to merge
      • important to avoid exposing internal data structures as much as possible so implementation changes don’t affect the interface
      • make the common case as simple as possible
      • don’t force users to learn about rarely used features
    • Information hiding within a class
      • can use private methods
      • minimize the number of places where each instance variable is used
    • taking it too far
      • only needed when information is not needed outside its module
  6. General-Purpose Modules are Deeper
    • Intro
      • general-purpose vs specific is a common decision
      • hard to predict future
      • you may never use the general purpose facilities
      • make classes somewhat general purpose
      • try to keep the interface general
    • Generality leads to better information hiding
      • cleaner separation
      • one of the most important elements of software design is determining who needs to know what and when
    • questions to ask yourself
      • what is the simplest interface that will cover all my current needs
      • reducing methods only makes sense as long as the API for each individual method stays simple i.e. not adding a bunch of new arguments
      • in how many situations will this method be used?
      • method for one particular use is a red flag
      • is this API easy to use for my current needs?
      • if you have to write a lot of additional code to use a class for your current purpose it is a red flag
  7. Different Layer, Different Abstraction
    • Intro
      • software systems are composed of layers
      • each layer should provide a different abstraction from the layers above and below it
    • pass-through methods
      • when adjacent layers have similar abstractions
      • makes classes shallower and creates dependencies
      • the interface to a piece of functionality should be in the same class that implements the functionality
      • 1) expose lower level class directly to the caller of the higher level
      • 2) redistribute the functionality
      • 3) merge functionality
    • when is interface duplication ok?
      • 1) dispatcher which uses the arguments to select one of several other methods to invoke e.g. web servers
      • 2) interfaces with multiple implementations e.g. disk drivers for different operating systems
    • decorators
      • extends functionality
      • separate special purpose extension of a class from a more generic core
      • there are usually better alternatives
      • 1) could you add directly to the underlying class?
      • 2) if new functionality specialized for a use case, can you merge it with the use case?
      • 3) add to an existing decorator?
      • 4) does functionality need to wrap existing functionality or implement as a standalone class?
    • interface vs implementation
      • representations used internally should be different from abstractions that appear in the interface
    • pass through variables
      • may need to modify a large number of interfaces
      • some solutions:
      • 1) is there an object that is already shared by top/bottom method?
      • 2) global var? other problems using this?
      • 3) context object
      • context object can become a huge grab bag of data that creates many non-obvious dependencies
      • use immutable variables
  8. Pull Complexity Downwards
    • Intro
      • usually better for the developer to deal with complexity than users
      • more important for the module to have a simple interface than implementation
    • config parameters
      • examples of moving complexity upwards
      • some situations hard for low-level to know about the best policy
      • usually should avoid
      • think about if users will be better able to determine a value
      • compute reasonable defaults
    • taking it too far
      • makes sense in following cases
      • 1) complexity being pulled down is closely related to the class’s existing functionality
      • 2) pulling down results in many simplifications elsewhere
      • 3) simplifies the interface
      • not simplifying higher level code and non-related functionality may be information leakage
  9. Better Together Or Better Apart?
    • Intro
      • given two functionality, should we implement together or apart?
      • goal is to reduce complexity
      • act of subdividing creates additional complexity
      • more components adds complexity, more interfaces
      • can result in more code
      • separation can make it hard to see components at the same time
      • can result in duplication
      • bringing together better if closely related
      • 1) shares info
      • 2) used together, but bidirectionally, not one way
      • 3) overlaps conceptually
      • 4) hard to understand without looking at the other
    • bringing together if information is shared
      • do both methods end up with considerable knowledge of one thing?
    • bringing together if it simplifies the interface
      • easier to use?
      • may be able to perform some functions automatically
      • hide info
    • bringing together to eliminate duplication
      • factor out repeated code
      • most effective if the repeated code is long and method has a simple signature
      • can also refactor so snippet only needs to be executed once
    • separate general-purpose and special purpose
      • lower layer generally more general purpose
      • pull special-purpose code upwards
    • logging
      • special class for logging errors
      • if the class is shallow, can result in additional complexity with no benefit
    • splitting and joining methods
      • don’t break up unless makes the overall system simpler
      • each method should do one thing and do it completely
      • splitting up only makes sense if it results in cleaner abstractions
      • child method generally more general-purpose
      • if have to flip back and forth between parent and child method to understand how they work together, splitting is probably a bad idea
      • may want to break up if the interface is overly complex
      • good if split methods more general purpose
      • should be possible to understand each method independently
    • conclusion
      • pick structure that results in best information hiding, fewest dependencies, and deepest interfaces
  10. Define Errors Out of Existence
    • Intro
      • one of the worst sources of complexity
      • reduce the number of places where exceptions must be handled
    • Why exceptions add complexity
      • an uncommon condition that alters normal control flow
      • first approach is to keep moving forward and try to complete
      • second approach is to abort
      • exception handling creates opportunities for more exceptions
      • hard to ensure exception handling code is working correctly
      • code that hasn’t been executed doesn’t work
    • Too many exceptions
      • exacerbate problems by handling unnecessary exceptions
      • if you’re having trouble figuring out what to do, there is a good chance the caller won’t know either
      • exceptions thrown by a class are part of its interface
      • classes with a lot of exceptions have a complex interface and are shallower than classes with fewer exceptions
      • throwing exceptions is easy, handling them is hard
      • the best way to reduce the complexity from exception handling is reduce the number of places where exceptions have to be handled
    • define errors out of existence
      • define your API so there are no exceptions to handle
      • e.g. return without doing anything
    • example: file deletion in windows
      • Windows does not permit a file to be deleted if it is open in a process
      • the Unix operating system does not delete the file immediately, instead, it marks it for deletion, removes from directly and deletes after closed
    • example: java substring method
      • if using ranges out of range, get IndexOutOfBoundsException
      • easier if perform adjustment automatically
      • e.g. python returns an empty result for out-of-range list slices
      • overall, the best way to reduce bugs is to make software simpler
    • mask exceptions
      • exception detected and handled at a low level
      • common in distributed systems
      • e.g. network transport e.g. TCP
      • doesn’t work in all situations, but powerful when it does
      • example of pulling complexity downward
    • exception aggregation
      • handle many exceptions with a single piece of code
      • instead of catching in the individual service methods, let them propagate up to the top-level dispatch
      • if a system processes a series of requests, useful to define an exception that aborts the current request, cleans up system state, and continues with next request
      • aggregation works best if exception propagates several levels up allowing more the be handled in one place
    • just crash?
      • e.g. not much an application can do if out of memory
      • IO errors
      • inconsistent data structures
      • whether to crash depends on use case e.g. don’t crash on I/O error in a replicated storage system
    • design special cases out of existence
      • design the normal case in a way that automatically handles the special cases without any extra code
    • taking it too far
      • defining away exceptions only makes sense if the exception information isn’t needed outside the module
      • in some cases, it is essential to expose the exceptions even if it adds complexity
      • as with other areas of software design, must determine what is important and what isn’t
    • conclusion
      • special cases of any form make code harder to understand and increases the likelihood of bugs
      • the best way to do this is by redefining semantics to eliminate error conditions
      • if you can’t define away, try to mask at a low level so the impact is limited
      • can also aggregate several special-case handlers into a single more general handler
  11. Design it Twice
    • unlikely first thought will produce best design
    • end up with better result if you consider multiple options
    • try to pick approaches that are radically different from each other
    • even if you are certain there is only one reasonable approach, consider anyway no matter how bad you think it is
    • make a list of pros and cons
    • the most important consideration for an interface is ease of use for higher level software
    • sometimes none of the alternatives are attractive
    • use problems you identified to try to drive new designs
    • can use this technique for many layers e.g. interface first then implementation
    • for implementation, most important things are simplicity and performance
    • design time small compared to days/weeks of implementation
    • better design will more than pay for time spent on it
  12. Why Write Comments? The Four Excuses
    • Intro
      • in-code documentation plays a crucial role in software design
      • process of writing comments, if done correctly, will actually improve a system’s design
      • viewed not universally shared
      • inadequate documentation creates a huge and unnecessary drag on software development
    • Good code is self-documenting
      • there is a significant amount of design information that can’t be represented in code
      • e.g. rationale for a particular design or conditions under which it makes sense to call a particular method
      • for large systems not practical for users to read the code to learn the behavior
      • having to learn how the code works from only reading it may be time-consuming and painful
      • if a user must read the code to use it, there is no abstraction
      • comments allow us to capture additional information that the caller needs
      • if you want to use abstractions to hide complexity, comments are essential
    • I don’t have time to write comments
      • if de-prioritized, you’ll end up with no documentation
      • consider an investment mindset
    • comments get out of date and become misleading
      • keeping up to date does not require an enormous effort
      • large changes only required if large changes to the code
      • code reviews are a great mechanism for detecting and fixing stale comments
    • all the comments I have seen are worthless
      • solvable problem
      • writing solid documentation is not hard, once you know how
    • benefits of well-written comments
      • the overall idea behind comments is to capture information that was in the mind of the designer but couldn’t be represented in the code
      • without documentation, future developers will have to guess at the developer’s original knowledge
      • complexity manifests itself in change amplification, cognitive load, and unknown unknown
      • comments help with the last two
  13. Comment Should Describe Things that Aren’t Obvious from the Code
    • intro
      • comments should describe things that aren’t obvious from the code
      • a comment can make the rule explicit and clear
      • developers should be able to understand the abstraction provided by a module without reading any code other than it’s externally visible declarations
    • Pick conventions
      • conventions ensure consistency and ensure you actually write comments
      • types of comments;
        • interface: immediately precedes the declaration of a module such as a class, data structure, function, etc. describes behavior, arguments, return value, side effects, exceptions, and other requirements
        • data structure member: comment next to the declaration of a field in a data structure
        • implementation comment: how the code works internally
        • cross-module comment: describing dependencies that cross module boundaries
      • First two most important
      • every class should have an interface comment, every class variable should have a comment, and every method should have an interface comment
      • implementation comments often unnecessary
    • Don’t repeat the code
      • ask yourself, could someone who has never seen the code write the comment just by looking at the code?
      • use different words in the comment from those in the name of the entity being described
    • lower-level comments add precision
      • comments augment the code by providing information at a different level of detail
      • lower level add precision and higher level add intuition
      • precision useful when commenting variable declarations
        • what are the units?
        • boundary conditions? inclusive or exclusive?
        • if null is permitted, what does that imply?
        • if a variable refers to a resource that must be freed, who is responsible for freeing or closing it?
        • are there certain properties that are always true for the variable (invariants)?
      • when documenting a variable, think nouns (what does it represent) not verbs
    • higher-level comments
      • help the reader understand overall intent and structure
      • more difficult to write
      • great software designers can also step back from the details and think about a system at a higher level
      • comments of the form “how we get here” are very useful for helping people understand the code
    • interface documentation
      • one of the most important roles for comments is to define abstractions
      • if you want code that presents good abstractions, you must document those abstractions with comments
      • separate interface comments from implementation comments
      • so users are not exposed to implementation details
      • if interface comments must also describe the implementation, then the class or method is shallow
    • implementation comments
      • the main goal of implementation comments is to help readers understand what the code is doing
      • add a comment before each of the major blocks to provide a high-level (more abstract) description of what the block does
      • describe what the code is doing and why
      • document any tricky aspects of the code that won’t be obvious from reading it
      • for longer methods, can be helpful to write comments for a few of the most important local variables
      • if the variable is used over a large span of code, then you should consider adding a comment to describe the variable
      • focus on what variable represents
    • cross-module design decisions
      • cross-module decisions are often complex and subtle and account for many bugs so good documentation for them is crucial
      • the biggest challenge is finding a place to put it where it will naturally be discovered by developers
      • one option is duplicating documentation, but awkward and hard to keep up to date
      • another approach is keeping a document in a central file called “designNotes”
      • any piece of code that relates to something in there can have a comment that references that document
      • a disadvantage is that the documentation is not near the code so may be difficult to keep up-to-date
    • conclusion
      • when following the rule that comment should describe things that aren’t obvious, “obvious” is from the perspective of the reader
      • if a reader thinks it’s not obvious, then it’s not obvious
  14. Choosing Names
    • intro
      • one of the most underrated aspects of software design
      • good names are a form of documentation
      • name choice is an example of the principle that complexity is incremental
    • example: bad names cause bugs
      • can cause mental blocks or force mind to see things in a certain way
    • create an image
      • the goal is to create an image in the mind of the reader about the nature of the thing being named
      • names can become unwieldy if they contain more than two or three words
    • names should be precise
      • most common problem is names that are too generic or vague
      • you might think they could relate to many different things
      • if you find it difficult to come up with a name, that is a red flag
      • suggests the variable may not have a clear definition or purpose
    • use names consistently
      • consistent naming reduces cognitive load
      • 1) always use the common name for the given purpose
      • 2) never use the common name for anything other than the given purpose
      • 3) make sure that the purpose is narrow enough that all variables with the name have the same behavior
      • sometimes will need multiple variables that refer to the same general sort of thing
      • use the common name, but add a distinguishing prefix e.g. srcFileBlock or dstFileBlock
    • a different opinion: Go style guide
      • some Go developers argue names should be very short
      • readability must be determined by readers not writers
    • conclusion
      • developing the skill for naming is also an investment
      • as you get more experience, becomes easier
      • get to point where it takes almost no extra time so you’ll get the benefits almost for free
  15. Write The Comments First
    • the best time to write comments is at the beginning of the process
    • delayed comments are bad comments
      • often means they never get written at all
    • write the comments first
      • produces better comments
      • if you write comments as you are designing the class, key design issues will be fresh in your mind
    • comments are a design tool
      • improves system design
      • if you write comments describing the abstractions at the beginning, you can review and tune them before writing implementation code
      • if variable or method requires a long comment, red flag
    • early comments are fun comments
      • comments are how I record and test the quality of my design decisions
      • looking for the design that can be expressed completely and clearly in the fewest words
      • simpler the comments, simpler the design
    • are early comments expensive?
      • writing the comments first will mean that the abstractions will be more stable
    • conclusion
      • if you haven’t tried, give it a try
      • think about how it will affect the quality of your comments, design, and overall enjoyment of the software development process
  16. Modifying Existing Code
    • stay strategic
      • in strategic programming, the most important goal is to produce a great system design
      • tactical programming leads to a messy system design
      • typical mindset is what is the smallest possible change that I can make that does what I need?
      • ideally, after the change, the system will have structure as if you designed it from the start with that change
      • every development organization should plan to spend a small fraction of its total effort on clean up and refactoring
    • maintaining comments: keep the comments near the code
      • inaccurate comments are frustrating to the readers
      • the best way to ensure they get updated is to position them close to the code they describe
      • when writing implementation comments, spread them out
    • comments belong in the code, not the commit log
      • developers who need the information is unlikely to think to scan the repository logs
      • most important to get into the code
      • place documentation in the place where developers are most likely to see it
    • maintaining comments: avoid duplication
      • try to document each design decision exactly once
      • don’t re-document one module’s design decisions into another module
      • just reference the external documentation
      • important that readers can easily find all the documentation needed to understand your code
    • maintaining comments: check the diffs
      • scan overall all the changes for that commit
      • make sure each change is properly reflected in the documentation
    • higher-level comments are easier to maintain
      • do not reflect the details of the code
  17. Consistency
    • intro
      • if not consistent, developers must learn about each situation separately and take more time
    • examples of consistency
      • names, coding style, interfaces, design patterns, invariants
    • ensuring consistency
      • create a document that lists most important overall conventions
      • write a tool that checks for violations
      • code reviews offer another opportunity for enforcing the convention
      • the more nit-picky, the more quickly everyone on the team will learn the conventions
      • when in Rome, do as the Romans do
      • don’t change existing conventions
      • a lot of value in consistency
      • reconsidering established conventions is rarely a good use of developer time
    • taking it too far
      • don’t try to force dissimilar things into the same approach
    • conclusion
      • investment mindset
      • the code will be more obvious
      • developers will be able to understand the code’s behavior more quickly and accurately
      • work faster with fewer bugs
  18. Code Should be Obvious
    • intro
      • obscurity is one of the two main causes of complexity
      • the solution is to write code in a way that makes it obvious
      • if the code isn’t obvious, the reader must expend a lot of time and energy to understand it
      • the best way to determine obviousness is through code reviews
      • if someone reading your code says it’s not obvious, it’s not obvious
    • things that make code more obvious
      • choosing good names
      • consistency
      • judicious use of white space
      • when you can’t avoid code that is nonobvious, use comments to provide missing information
    • things that make code less obvious
      • event-driven programming
      • to compensate, use interface comment for each handler to indicate when it is invoked
      • generic containers e.g. tuples
      • if you need a container, define a new class or structure that is specialized for a particular use case
      • software should be designed for ease of reading not ease of writing
      • code that violates reader expectations e.g. starting processes that aren’t closed when the application main ends
      • important to document these cases
    • conclusion
      • if code is nonobvious, there is important information about the code that the reader doesn’t have
      • 1) use design techniques such as abstraction and eliminating special cases
      • 2) take advantage of information that readers have already acquired in other contexts
      • 3) present important information to them in code, using good names and strategic comments
  19. Software Trends
    • Object-oriented programming and inheritance
      • one of key elements is inheritance
      • first form is interface inheritance
      • parent class defines the signatures and subclass must implement
      • providers leverage against complexity by reusing the same interface for multiple purposes
      • second form is implementation inheritance
      • creates dependencies
      • results in information leakage and makes hard to modify one class in the hierarchy without looking at the others
      • use implementation inheritance with caution
      • first consider an approach based on composition
      • if you have to use, try to separate the state managed by the parent class from that managed by the subclass
      • OOP can assist in clean design, but does not guarantee it
      • if classes are shallow, have complex interfaces, permit external access to internal state will still result in high complexity
    • Agile development
      • one of most important elements is that development should be incremental and iterative
      • isn’t possible to visualize a complex system well enough at the outset of a project to determine the best design
      • best way to end up with good design is to develop a system in increments where each increment adds a few new abstractions and refactors existing abstractions
      • risk of agile is can lead to tactical programming leading to rapid accumulation of complexity
      • developing incrementally is generally a good idea, but increments should be abstractions not features
    • unit tests
      • they facilitate refactoring
      • without test suite, dangerous to make major structural changes
      • can be more confident in making structural improvements
    • test-driven development
      • not a fan
      • problem with test-driven development is that it focuses attention on getting specific features working, rather than finding the best design
      • one place where it makes sense is when fixing bugs
      • make sure you can reproduce the failure so you know that you fixed it
    • design patterns
      • generally good, but need to use in the correct situations
      • greatest risk is over-application
      • using design patterns doesn’t automatically improve a software system
    • getters and setters
      • better not to expose instance variables in the first place
      • exposed instance variables mean that a part of the class’s implementation is visible externally
      • getters and setters are shallow methods
    • conclusion
      • whenever you encounter a proposal, challenge it from the standpoint of complexity
      • does it help minimize complexity?
  20. Designing for Performance
    • Intro
      • the important idea is still simplicity
    • how to think about performance
      • if you try to optimize every statement for maximum speed, will slow down development and create a lot of unnecessary complexity
      • on the other hand, if you completely ignore, can easily be 5-10x slower in “death by a thousand cuts”
      • can be hard to come back alter and improve because of no single improvement to make
      • the best approach is something between these extremes
      • choose design alternatives that are naturally efficient
      • the key is to develop an awareness of which operations are fundamentally expensive e.g. network communications, I/O, dynamic memory allocation, cache misses
      • best way to learn which things are expensive is to run micro-benchmarks
      • if the only way to improve efficiency is by adding complexity, then the choice more difficult
      • if adds only a small amount of complexity and if complexity is hidden, so it doesn’t affect any interfaces, may be worthwhile
      • if faster design adds a lot of implementation complexity, may be better to start off with the simpler approach and optimize later
    • measuring before modifying
      • don’t rush off and start making performance tweaks
      • programmers’ intuitions about performance are unreliable
      • this is true even for experienced developers
      • first measure systems’ existing behavior
      • the goal is to identify a small number of very specific places
      • there’s no point in retaining complexity unless it provides a significant speedup
    • design around the critical path
      • the best way to improve its performance is with a “fundamental” change such as introducing a cache, or using a different algorithmic approach
      • sometimes there isn’t a fundamental fix
      • how to redesign an existing piece of code so it runs faster
      • start off by asking yourself what is the smallest amount of code that must be executed to carry out the desired task in the common case
      • consider only the data needed for the critical path
      • remove special cases from the critical path
      • each special case adds a little bit of code to the critical path
      • ideally, have a single if statement at the beginning which detects all special cases
    • conclusion
      • clean design and high performance are compatible
  21. Conclusion
    • this book is about complexity
    • dealing with complexity is the most important challenge in software design
    • offered some general ideas: deep and generic classes, define errors out of existence, and separate interface and implementation documentation
    • the downside is can create extra work in early stages
    • if you aren’t used to it, can slow you down
    • design is a fascinating puzzle
    • how can I solve a particular problem with the simplest possible structure?
    • clean, simple, and obvious design is a beautiful thing
    • investments you make will pay off quickly in the future
    • as you hone your design skills, you will find you can produce good designs more and more quickly
    • the reward for being a good designer is that you get to spend a larger fraction of your time in the design phase, which is fun
    • poor designers spend most of their time chasing bugs in complicated and brittle code
    • if you improve your design skills, not only will you produce higher quality software, but the software development process will be more enjoyable
Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s