Skip to content

Instantly share code, notes, and snippets.

@alvinncx
Last active September 29, 2020 06:03
Show Gist options
  • Save alvinncx/e911f3741c649f65f97b949ffa44e45e to your computer and use it in GitHub Desktop.
Save alvinncx/e911f3741c649f65f97b949ffa44e45e to your computer and use it in GitHub Desktop.
Summary of Code Complete 2

Design in construction

  • Software’s Primary Technical Imperative is managing complexity. This is greatly aided by a design focus on simplicity.
  • Simplicity is achieved in two general ways: minimizing the amount of essential complexity that anyone’s brain has to deal with at any one time, and keeping accidental complexity from proliferating needlessly.
  • Design is heuristic. Dogmatic adherence to any single methodology hurts cre- ativity and hurts your programs.

Information Hiding

  • Foundation of OOP and structured design
  • Thinking in objects vs thinking in information hiding
    • Hiding abstracts complexity and delays lock in; thinking in OOP locks you into smaller set of decisions
      • e.g. Should something be declared as a new class
    • Information Hiding is consistent with OOP

Separate and Isolate

Accommodating change is a big challenge of software design. Isolate instabilities and changes.

  • Business rules
  • Hardware dependencies
  • Input and output
  • Non standard language features
  • Difficult design and prototypes
  • Status variables
    • Don't use booleans, try enumerated types
    • Use access routines
  • Data size constraints

Loose coupling

A "loose coupling" between two classes is characterised by small, direct, visible and flexible relations.

Measuring coupling

  • Size
    • Fewer args is better
    • Fewer public methods is better
  • Visibility
  • Flexibility
    • How easily one module uses another
    • The easier the less coupled

Major categories of coupling

  • Simple-data-parameter coupling
    • Data passed between are primitive data types, all passed though parameter lists
  • Simple-object coupling
    • Instantiates the object
  • Object-parameter coupling
    • Obj1 requires Obj2 to pass to Obj3.
  • Semantic coupling
    • Dangerous because it can break code in ways that are not detectable by compiler
    • One module makes use not of some syntactic element of another module but of some semantic knowledge of another module’s inner workings.

Use design patterns

  • Reduces complexity through ready-made abstractions
  • Institutionalizing details of common solutions
    • Conceptually similar to using a library
  • Patterns streamline communication by moving the design dialog to a higher level

Use different design appraoches

  • Top down
    • Defer construction details
    • Divide and conquer
  • bottom up
    • Encourages reuse of existing modules
    • Compact well factored design
  • Experimental prototyping
    • Important to treat code as throwaway
  • Collborative design with co workers
    • Informal, bounce ideas with one another

Working Classes

Abstract Data Types

Use ADT to manipulate real-world entities rather than low-level, implementation entities.

Benefits to using ADT

  • Hide implementation details
  • Isolate changes within ADT
  • Interfaces become more informative
  • More obviously correct
  • More self documenting
  • Don't have to pass data all over the program
  • Reason around using real-world entities instead of low level implementation structures

Good practices with ADT

  • Use low level data types as ADT, not low-level data types
    • If employees are a lowest level business unit use Employee, not a hash map.
  • Treat even simple items as ADT

Dealing with instances in non-OOP

  • Option 1: Explicitly identify instances each time you use ADT
    • e.g. Pass in ID.
    • No notion of current instance
  • Option 2: Explicitly provide the data used by the ADT services
    • e.g. Pass in Data Struct
    • Data is exposed to the rest of program
  • Option 3: Use Implicit instances
    • e.g. Set current instance then do operations on it using service
    • Not safe

Good Abstractions

  • Good abstractions should work towards a consistent end
    • An indication appears to be public method names that are business driven instead of low level operations.
  • Abstractions should be consistent
    • Should not have to implement 2 types of ADT in a same class
  • Provide services in pair
    • Most services have actions that act in pairs
      • Add, Remove
      • On, Off
  • Interfaces should be enforced by compiler instead of semantic considerations
    • Semantic part of an interface consists of assumptions about how an interface is used, but cannot be enforced by compiler.
    • E.g. business logic in the comments
    • If no choice, enforce semantic requirements using Asserts
  • Abstraction and cohesion go together hand in hand

Good Encapsulation

  • Either you have abstraction and encapsulation or you have neither. There is no middle ground.
  • Minimise accessiblity of classes and members
  • Don't expose member to public
    • float x exposes the nature of x.
    • float GetX() and void SetX() doesn't expose the implementation of x.
  • Avoid putting private implementation in a class interface
    • In C++, can point to a different class using a pointer
  • Don't make assumptions about a class's users
  • Avoid Friend classes
  • Be careful of semantic violations of encapsulation
  • Syntatatically it is easy by just devalring private
  • But semantically, it is difficult. Easy to assume no need to do something because it has been done at some other part of the programme already.

When proper encapsulation is achieved, you should be programming to the interface, not through it.

Design and Implementation issues

Containment

  • The idea of has a
  • Only implement has a though private inheritance as last resort
    • Usually leads to over coupling with ancestor class, violating the encapsulation
  • Be critical of classes that contain more than 7 data members

Inheritance

The single most important rule in object-oriented programming with C++ is this: public inheritance means “is a.” Commit this rule to memory.

  • The new class "is a" more specialised class of the older class.
  • If the derived class is not going to adhere completely to the same interface contract defined by the base class, consider other design techniques.
  • Design and document for inheritance, or prohibit it.
  • Must strictly adhere to Liskov Substitution Principle

Example where LSP is broken (different semantics)

SavingsAccount << CheckingAccount 
AutoLoanAcccount << CheckingAccount 

interestRate() on CheckingAccount & SavingsAccount returns interest rate that bank pays
interestRate() on AutoLoanAccount is the interest rate that customer pays to bank
  • Inherit only what you want
    • Routines can be Abstract overridable, Overridable or Non-overridable
    • If you only need the class's implementation, but not the interface, consider containment.
  • Don't override private methods
  • Move common interfaces as high as possible
  • Don't design for the future
    • e.g. base classes with one derived class
  • Avoid deep inheritance trees
    • max 3 levels deep and max 7 sub classes

Prefer Polymorphism over extensive cases

  • If code is executed according to the type of class, perhaps can consider polymorphism
    • code smell: case statements

Mixins

Great for including multiple small behaviors into an existing class.

  • examples: Displayable, Persistent, Sortable
  • mixins are nearly always abstract

Rules for inheritance

  • classes share same data but not interface > contain the data in a common object
  • classes share same behavior but not data > inherit from base class
  • classes share same behavior and data > inherit from base class and data

Creating classes

  • Modelling real world
  • Model abstract objects
  • Reduce complexity
  • Hiding global data
  • Streamline parameter passing
  • Package related operations

Classes to avoid

  • God classes
  • Classes with only data
    • More likely what is needed is a struct
  • Classes with only routine

High Quality Routines

  • Use routines to introduce an immediate, understandable abstraction
  • Use routines to hide sequences
  • Use routines to hide pointer operations
  • Simplify complicated boolean tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment