A Philosophy of Software Design

A Philosophy of Software Design

Credit & Author: John Ousterhout

---

Preface


Chapter 1: Introduction

Eliminating Complexity

  1. Make code simpler and more obvious
  2. Encapsulate complexity so that the programmer can work on a system without being exposed to all of its complexity at once (modular design)

Beautiful design reflects a balance between competing ideas and approaches


Chapter 2: The Nature of Complexity

Your job as a developer is not just to create code that you can work with easily, but to create code that others can also work with easily

Symtoms of Complexity

One of the most important goals of good design is for a system to be obvious. In an obvious system, a developer can quickly understand how the existing code works and what is required to make a change

Change Amplification

Cognitive Load

Sometimes an approach that requires more lines of code is actually simpler because it reduces cognitive load

Unknown Unknowns

With unknown unknowns, it is unclear what to do or whether a proposed solution will will work

Causes of Complexity

Dependencies

Obsecurity

Complexity is Incremental

It's easy to convince yourself that a little bit of complexity introcued by your current change is no big deal. However, if every developer takes this approach for every change, complexity accumulates rapidly


Chapter 3: Working Code Isn't Enough

Many organizations encourage a tactical mindset, focused on getting features working as quickly as possible. However if you want a good design, you must take a more strategic approach when you invest time to produce clean designs and fix problems


Chapter 4: Modules Should Be Deep

Design systems so that developers only need to face a small fraction of the overall compelxity at any given time

Modular Design

  1. interface consists of everything that a developer working in a different module must know in order to use the given module
  2. implementation consists of code that carries out the promises made by the interface

Interface

Abstractions

Deep Modules

The best modules are deep: they have a lot of functionality behind a smiple interface

Classitis


Chapter 5: Information Hiding (and Leakage)

Technique for achieving deep modules where the implementation is hidden and not exposed in the interface

Information Hiding

  1. Simplier interface to a module reduces cognitive load
  2. Easier to evolve system as design change related to information will only affect one module

Information Leakage

Same knowledge used in multiple places

Temporal Decomposition

Other Topics


Chapter 6: General-Purpose Modules are Deeper

Questions to Ask

  1. What is the simplierst interface that will cover all my current needs?
  1. In how many situations will this method be used?
  1. Is this API easy to use for my current needs?

Chapter 7: Different Layer, Different Abstraction

Software systems are composed in layers, where higher layers use the facilities provided by lower layers. In a well-designed system, each layer provides a different abstraction from the layers above and below it. If adjacent layers have similar abstractions, we signals that we could have a problem with class decomposition

Pass-through Methods

  1. Expose lower level class directly to the callers of the higher level class
  2. Remove all responsibility for feature from higher level class
  3. Redistribute functionality between classes
  4. If classes can't be disentangled, merge them

When is interface duplication OK?

Decorators

take an existing object and extend its functionality by providing an API similar to the underlying object

Interface versus Implementation

The interface of a class should normally be different from its implementation: the representations used internally should be different from the abstractions that appear in the interface

Pass-through Variables

  1. Is there an object shared between the topmost and bottommost methods?
  2. Store information in a global variable, but has its own problems
  3. Context object that stores application's global state
    • allows multiple instances of the system to coexist in a single process, each with its own context
    • context will be needed many places so try to save it in system's major objects (include in constructors)
    • makes it easy to identify and manage global state of system
    • simplifies testing
    • make context variable immutable to make threadsafe

Chapter 8: Pull Complexity Downwards

More important for a module to have a simple interface than a simple implementation

Configuration Parameters

Rule of Thumb

  1. is closely related to the class' existing functionality
  2. will result in many simplifications elsewhere in the applciation
  3. simplifies the class' interface

Chapter 9: Better Together Or Better Apart?

Given two pieces of functionality, should they be implemented together in the same place, or should their implemtnations be separated?

Bring Together: Simplify the Interface

Bring Together: Eliminate Duplication

Separate General-Purpose and Special-Purpose Code

Splitting and Joining Methods

When designing methods, the most important goal is to provide clean and simple abstractions


Chapter 10: Define Errors Out of Existence

Exception handling is one of the worst sources of complexity in software systems

Why exceptions add complexity

  1. move forward and complete the work in progress in spite of the exception
  2. abort the operation in progress and report the exception upwards
    • aborting can result in an inconsistent system state so the exception handling code must restore consistency

Too many exceptions

Define errors out of existence

Mask Exceptions

Exception aggregation

Crash Application

Design special cases out of existence

Taking it too far


Chapter 11: Design it Twice

designing software is hard, so it's unlikely that your first thought about how to structre a module or system will produce the best design

Ego


Chapter 12: Why Write Comments? The Four Excuses

Good code is self-documenting

If users must read the code of a method in orer to use it, then there is no abstraction

I don't have time to write comments

Comments get out of date and become misleading

Benefits of well-written comments

Comments capture information that was in the midn of the designer but couldn't be represented in the code


Chapter 13: Comments Should Describe Things that Aren't Obvious from the Code

Pick conventions

Don't repeat the code

Could someone who has never seen the code write the comments just by looking at the code next to the comment?

Lower-level comments add precision

Lower-level comments offer precision by clarifying exact meaning

Higher-evel comments enchance intuition

High-level comments offer intuition by describing reasoning behind the code

Interface documentation

Provide information that someone needs to know in order to use a clas or method

Implementation comments: what and why, not how

Help readers understand what code is doing and why it was done that way

Cross-module design decisions


Chapter 14: Choosing Names

Selecting names for variables, methods, and other entities is one of the most underrated aspects of software design. Good names are a form of documentation: they make code easier to understand. They reduce the need for other documentation and make it easier to detect errors. Conversely, poor name choices increase the complexity of code and create ambiguities and misunderstandings that can result in bugs. Name chocie is an example of the principle that complexity is incremental. Choosing a mediocre name for particular variable, as opposed to the best possible name, probably won't have much impact on the overall complexity of a system. However, software systems have thousands of variables; choosing good names for all of these will have a significant impact on complexity and manageability.

Create an image

Names should be precise

Use names consistently

  1. always use the common name for a given purpose
  2. never use the common name for anythign other than the given purpose
  3. make sure the purpose is narrow enough that all variables with the name have the same behavior

More Thoughts


Chapter 15: Write The Comments First

Use comments as part of the design process

  1. produces better comments as key design issues are fresh in your mind
  2. comments are an abstraction and can help indicate complexity before you write code
  3. it makes things more fun (calm down, John Ousterhout)

Delayed comments are bad comments

Write the comments first


Chapter 16: Modifying Existing Code

Stay Straetgic

Maintaining comments: keep the comments near the code

Comments belong in the code, not the commit log

Maintaining comments: avoid duplication

Maintaining comments: check the diffs

Higher-level comments are easier to maintain


Chapter 17: Consistency

Ensuring Consistency


Chapter 18: Code Should be Obvious

Software should be designed for ease of reading, not ease of writing

Things that make code more obvious

Things that make code less obvious


Chapter 19: Software Trends

Object-oriented programming and inheritance

  1. Interface Inheritance
    • parent class defines the signature for one or more methods, but does not implement the methods
    • provides leverage against complexity by reusing the same interface for multiple purposes
    • many different implementations of interface means it is deep
  2. Implementation Inheritance
    • parent class defines not only signatures, but also default implemtnations
    • reduces the amount of code that needs to be modified, but creates dependencies between parent and subclasses
    • look into composition before using implementation inheritance

Agile Development

Unit Tests

Test-driven Development

Design Pattern


Chapter 20: Designing for Performance

Clean design and high performance are compatible