02. Principles

Controversial topic debates: defending extreme viewpoints is difficult and requires research to justify the viewpoint.

Software engineering principles not black and white:

Technical Debt

Design decisions made in the past under circumstances that are no longer relevant Conscious, un-ideal decisions made in the past that must be corrected

Ward Cunningham, 1992: quick and easy approach that comes with interest - additional work that must be done in the future: the longer you wait, the more code relies on the debt - interest grows over time.

Design stamina hypothesis (Martin Fowler): in a time-functionality graph:

Types of Technical Debt

Two axes: deliberate/inadvertent, and reckless/prudent

Reasons for Debt

Caused by:

Measuring Technical Debt

302: a lot of debt by the end of the year.

Measure how much technical debt there is by:

Types of debt:

Interest rates:

Positive/negative value, visible/invisible attributes:

Pick a process/framework (Scrum/Kanban/Waterfall): Which part is devoted to Technical Debt correction/payment?

Fan-in vs Fan-out

Refactor vs Re-engineering

Hence, refactorings should be done as-you-go while re-engineerings should be done infrequently and only after careful planning.

Reuse vs KISS

Object-oriented programming built on:

But reuse didn’t work - requirements for each program and the abstractions required differ.

Reuse is big design up front:

Unfortunately, determining the ‘correct’ design is impossible until implementation.

Situations when reuse does work:

Sapir-Whorf hypothesis/linguistic relativity: the structure of a language influences how you think. In programming terms: the programming paradigms we are used to influence our mindset and how we solve problems.

Reuse requires generic and abstract code/thinking:

KISS:

Design Principles

Encapsulation vs Information Hiding

Encapsulation is a tool to draw a border around a module.

Information hiding is a principle where you hide internal details from the outside world. This can be done using encapsulation.

This is used to hide what varies; anything that could be changed should be hidden (e.g. algorithm used for sorting).

Hence, argument and return types should be as high/generic as possible (eg. return Collection instead of ArrayList).

If a property or method is private, the type doesn’t matter as the type is encapsulated anyway.

Visibility, Access Levels, Modifiers

‘Never use public properties; use getters and setters instead’.

Getters and setters; two extreme viewpoints:

Coupling & Cohesion

Coupling: the extend to which two modules depend on each other.

Cohesion: how well the methods and properties within a module belong with each other.

Aim for high cohesion, low coupling.

Principle: keep data and behavior together (i.e. high cohesion).

The principle of separation of concerns separates data and behavior, but puts the related behaviors together.

The SOLID Principles

Single Responsibility Principle (SRP)

Each thing should only be in charge of one thing.

A responsibility = a reason for the module to change.

The SRP conflicts with the modeling of the real world, where objects usually do more than two things:

In addition, applying the SRP mindlessly can lead to:

Figuring out what the Single Responsibility should be can often be difficult?

Robert Martin’s thoughts on SRP:

…This principle is about people.

When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function.

Imagine you took your car to a mechanic in order to fix a broken electric window. He calls you the next day saying it’s all fixed. When you pick up your car, you find the window works fine; but the car won’t start. It’s not likely you will return to that mechanic because he’s clearly an idiot.

Open/Closed Principle (OCP)

Modules should be open for extension, but closed for modification.

That is, you should be able to extend the behavior of an existing program without modifying it.

Interfaces are useful because they are an agreement that you will follow some defined behavior (for all public methods/properties); that is, Design-by-contract:

The open/closed principle forces abstractions and loose coupling and often requires dependency inversion.

Libraries and plug-in architectures are often good examples of OCP.

Can a program be fully closed? Probably not as this requires big design up-front.

Protected Variation: anything that is likely to change should be hidden and pushed downwards, with stable interfaces above/around them.

Liskov-Substitution Principle (LSP)

You should be able to change the subclass of an object without changing the behavior of the program i.e. design-by-contract: children adhere to their parent’s contract.

The LSP is not easy to implement and has no immediate benefits; rather, it gives long-term trust in modules.

Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces/methods they will not use:

Martin Fowler’s original article.

Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules: both should depend on abstractions/interfaces.

From this, the following follows:

Mostly taken for granted by the newer generation of programmers learning OO languages.

Common Closure Principle (CCP)

SRP at the package level: classes in a package should be closed together against the same kind of changes.

Common Reuse Principle (CRP)

Classes in a package are reused together: if you reuse one class, reuse all of them.

Classes being reused within the same context should be part of the same package.

e.g. Util package in Java.

Abstract Factory (AKA Kit Pattern)

Dependency inversion: client no longer needs to care about the specifics of the implementations.

Factories define an interface to instantiate new instances of a specific implementation of a class/interface, removing the need for a client to know the exact type being instantiated.

Hence, this is an example of dependency inversion as the client uses an interface to distance itself from the specific class and constructor being called.

An abstract factory takes this further by giving the factory interface methods to instantiate multiple related (and possibly dependent) objects.

The abstract factory keeps behavior, not data together.

Factory methods give looser coupling; details are (how the objects are instantiated) brought down to concrete classes, while interfaces are given to the higher layers (abstract classes)

The abstract factory is an example of parallel hierarchy: multiple hierarchies following the same structure. e.g.:

      Operator              Vehicle
   ______|______           ____|____
  ▽            ▽           ▽       ▽
Pilot        Cyclist     Plane    Bike

The factory method ensures the right operator is assigned to the vehicle. But what if you already have a specific operator you want to assign to the vehicle?

If you have a setOperator(Operator) method on the Vehicle interface, it defeats the point of the factory method. Rather, the concrete classes (Plane, Bike) must have setPilot(Pilot) and setCyclist(Cyclist) methods.

That is, go as high as you can in your hierarchy, but no further - there is no point raising it to the top if it means it fails to meet your requirements.

Stable Dependencies Principle (SDP)

Want stability; lack of changes, at the top of the hierarchy. See: hide what varies, contracts.

A module should depend on modules that are more stable than itself.

Maximum stability: if environment changes, module can’t change. Additionally requires big design up-front.

Should stability/instability be distributed across the entire program? No; some parts of the program will need to change frequently.

Stable Abstractions Principle (SAP)

A module should be as abstract as it is stable:

Tell, Don’t ask

Law of Demeter

If you have method M in object O, then M can call the methods of:

#noestimate

What does it mean?

Standard agile estimates story points to determine the number of stories done in the sprint and calculate their velocity.

#noestimate instead just completes tasks by priority and uses the tasks completed to calculate velocity. As the tasks are sliced vertically, the client gets a tangible end result at the end of each sprint.

So why estimate? The process (e.g. planning poker, discussion) is useful even if the estimates themselves are not.

Vertically slicing means:

Story mapping:

Class Debates

Always/Never Write Documentation

Always:

Never:

Always, counterpoints:

Never, counterpoints:

Collective vs Individual Code Ownership

Collective:

Individual:

Individual, counterpoints:

Collective, counterpoints: