Chapter 07
Object-oriented design and clean code
How to organise programs so they stay readable as they grow: classes where they help, clear boundaries, good names and the principles reviewers ask about.
Classes and objects
Also called OOP, instance, constructor, methods, object-oriented
- What
- A class is a blueprint that groups data (fields) with the behaviour that works on it (methods). An object is one instance of a class, created by its constructor.
- Why
- Use a class when data and the rules that change it belong together, such as an account whose balance only changes through deposits.
public class Account(string owner)
{
public string Owner { get; } = owner;
public decimal Balance { get; private set; }
public void Deposit(decimal amount)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
Balance += amount;
}
}Watch out
Making everything a class because a tutorial did; a plain function is often simpler.
Encapsulation
Also called information hiding, private, access modifiers
- What
- Keeping an object's data private and exposing only the operations that keep it valid.
- Why
- If only
depositcan change a balance, a negative deposit is rejected in one place instead of every caller having to remember the rule.
Watch out
Adding a public getter and setter for every field, which exposes the data as if it were public anyway.
Inheritance and composition
Also called extends, subclass, has-a vs is-a
- What
- Inheritance builds a class from a parent class and reuses its code ("is a"). Composition builds a class from other objects it holds and delegates to ("has a").
- Why
- Composition is usually more flexible: you can swap parts and avoid deep hierarchies. Inheritance fits true "is a" relationships with shared behaviour.
- Example
- A
ReportExporterthat holds aFormatter(CSV or PDF) can switch formats at runtime. ACsvReportExporter extends PdfReportExporterhierarchy cannot, and breaks when either parent changes.
Watch out
Inheriting just to reuse a few methods, which ties the child to every future change in the parent.
Interfaces and polymorphism
Also called abstraction, abstract class, abstract base class, duck typing, protocol
- What
- An interface is a contract listing methods a type must provide. Polymorphism means code written against the contract works with any type that fulfils it.
- Why
- It lets you add a new kind of notifier, payment method or storage without changing the code that uses it, and lets tests pass in fakes.
public interface INotifier
{
void Send(string to, string text);
}
public class EmailNotifier : INotifier
{
public void Send(string to, string text) =>
Console.WriteLine($"email {to}: {text}");
}
static void Remind(INotifier n, string user) =>
n.Send(user, "Your book is due");Watch out
Creating an interface for every class up front, when there is only ever one implementation.
Separation of concerns
Also called layers, modularity, single responsibility
- What
- Splitting a program so each part handles one concern: reading input, business logic, storage and output live in separate functions or modules.
- Why
- You can change the output format without touching the calculations, and you can unit test the logic without a terminal, file or network.
- Example
- A budget tracker with
parse_ledger(file to records),monthly_totals(records to numbers) andprint_report(numbers to text). Only the middle one needs detailed tests.
Watch out
Mixing print or database calls into calculation functions, so they cannot be tested or reused.
DRY, KISS and YAGNI
Also called DRY, KISS, YAGNI, Don't Repeat Yourself, do not repeat yourself, keep it simple, You Aren't Gonna Need It, you are not going to need it
- What
- DRY: keep each piece of knowledge in one place. KISS: prefer the simplest design that works. YAGNI: do not build features or flexibility until they are needed.
- Why
- Together they keep code small and changeable. Use them as questions while refactoring, not rules for a first draft.
Watch out
Forcing DRY on code that only looks similar, creating a shared function full of flags that is harder to change than the duplicates were.
Naming and small functions
Also called clean code, readability
- What
- Names that say what a value is or what a function does (
unpaid_invoices,send_reminder), and functions short enough to do one thing at one level of detail. - Why
- Code is read far more often than written. Good names remove the need for most comments and make bugs easier to spot.
- Example
- Rename
dtodays_overdueand extract the 20 lines that build the email intobuild_reminder_email(invoice). The main function now reads like a short list of steps.
Watch out
Vague names like data, temp, manager or handle, which hide what the code actually does.
SOLID
Also called single responsibility, open closed, Liskov substitution, interface segregation, dependency inversion
- What
- Five design principles: a class has one reason to change; extend behaviour without editing working code; subtypes must work wherever the parent does; keep interfaces small; depend on abstractions rather than concrete details.
- Why
- They are useful review questions when a design is getting hard to change or test, especially in object-oriented languages.
Watch out
Applying all five to a 200-line program, which adds layers of interfaces that make simple code harder to follow.
Code smells and refactoring
Also called refactor, technical debt
- What
- A code smell is a sign of a deeper design problem: long functions, duplicated blocks, many parameters, deep nesting. Refactoring improves the structure in small steps without changing behaviour.
- Why
- Regular small refactors keep code easy to change. Tests are what make refactoring safe.
Watch out
Refactoring and changing behaviour in the same commit, so when something breaks you cannot tell which change caused it.
Comments, docstrings and READMEs
Also called docs, Javadoc, XML doc comments, JSDoc
- What
- Comments explain why code does something non-obvious. Docstrings and doc comments describe what a function expects and returns. A README explains what a project is and how to run it.
- Why
- Good documentation lets someone else (or you in six months) use and change the code without reading all of it.
Watch out
Comments that repeat the code, such as i += 1 # add one to i, which add noise and go stale.
Go deeper