AI Study Notebook AI-generated
Study Guide: Smalltalk Best Practice Patterns
Kent Beck
By Best Books
This AI-generated study guide is a reading aid. The source-backed recommendation record and evidence for this book live on the book page.
On this page
Smalltalk Best Practice Patterns — Chapter-by-Chapter Outline
Author: Kent Beck First published: 1996 Edition covered: First edition (Prentice Hall, 1996; ISBN 978-0-13-476904-2). Only one edition exists; an e-book reprint was issued by Addison-Wesley Professional / Pearson in 2012 under ISBN 978-0-13-285209-8 with no chapter changes.
Central thesis
Every experienced Smalltalk programmer carries a large mental catalogue of micro-decisions: how to name a method, when to use a block versus a message send, how to break a big method apart, when lazy initialization beats explicit initialization. These decisions are made dozens of times per hour, but they are almost never written down. The result is that good Smalltalk style is transmitted only by apprenticeship, and beginners spend years reinventing solutions that experts already know.
Beck's central claim is that these micro-decisions can be captured as patterns — named, reusable solutions to recurring low-level design problems — and that a programmer who internalises a library of such patterns will write cleaner, more communicative, more maintainable code far faster than one who works from first principles each time.
The book is simultaneously a pattern catalogue and a philosophy of programming style. Its deeper argument is that code is a communication medium. The primary audience for code is not a computer but a future human reader, and every design decision — method length, variable name, choice of message — either helps or hinders that reader's understanding.
How do you consistently write code that others can understand, maintain, and extend?
Chapter 1 — Introduction
Central question
What problems does this book address, who is it for, and what does "good Smalltalk style" actually mean?
Main argument
The communication bottleneck
Beck opens by arguing that the biggest bottlenecks in software development are not algorithmic — they are communicative. Most professional time is spent reading, reviewing, and modifying existing code, not writing new code from scratch. A programmer who writes for future readers, not just for the interpreter, removes friction from the entire team's work.
Good software, defined
Beck identifies four properties that characterise good Smalltalk code:
- It is written once and only once — duplication is the enemy of maintainability.
- It communicates clearly to a reader — names carry intent, not implementation detail.
- It consists of many small, focused pieces — objects and methods do one thing each.
- It is easy to change — because each piece has a clear responsibility and low coupling.
Style versus rules
The book is not a rulebook. Beck distinguishes between rules (do this always) and patterns (do this when these forces apply). Style is the accumulated judgment of when each pattern is appropriate. He argues that a programmer's style is visible in every line of code and that developing good style requires conscious attention to the small decisions, not just the architectural ones.
What the book omits
Beck explicitly limits the book to coding patterns — the granular decisions made during implementation — and deliberately excludes higher-level design patterns (GoF-style), architecture, process, and testing. The scope is intentionally narrow so that the treatment can be thorough.
How to use the book
Three adoption strategies are offered:
- Bag of tricks: read selectively and apply patterns one at a time.
- Style guide: adopt it as an organisational standard.
- Whole hog: read cover-to-cover and apply everything immediately.
Beck recommends reading each pattern, looking for a place to apply it in existing code, and refactoring accordingly — learning through doing rather than through passive reading.
Key ideas
- The primary audience of code is a human reader, not a machine.
- Good code says everything once and only once; duplication signals a missing abstraction.
- Small methods and small objects are not an aesthetic preference — they are a practical consequence of the communication goal.
- Low coupling and high cohesion emerge naturally when each method and class does exactly one thing.
- Patterns are not rules to follow blindly; they are named solutions whose applicability depends on context.
- Style is developed by practicing pattern application on real code, not by memorizing definitions.
Key takeaway
The purpose of the book is to make explicit — and therefore learnable — the hundreds of small decisions that experienced Smalltalk programmers make automatically and consistently.
Chapter 2 — Patterns
Central question
What is a pattern, why do patterns work as a knowledge-transfer mechanism, and how should each pattern in this book be read?
Main argument
Why patterns work
Beck borrows from architecture (Christopher Alexander's pattern language work) but grounds his explanation in the economics of programming. Mature engineering disciplines build handbooks of adequate solutions to known recurring problems. This frees practitioners to focus attention on the genuinely novel aspects of each new project. Software has lagged in developing such handbooks because code reuse — libraries, frameworks — handles recurring implementations but not recurring design decisions. Patterns fill the gap between reusable code and reusable wisdom.
There are only so many things objects can do. Across wildly different application domains, the same structural and communicative problems recur: how to represent an optional attribute, how to handle type-sensitive arithmetic, how to signal that a query returned no result. A programmer who has solved each problem before brings a decisive advantage to every new problem. A catalogue of named solutions is a shortcut to that advantage.
The role of patterns in practice
Patterns serve multiple functions beyond the moment of coding:
- Reading: named patterns let a reader instantly recognise a familiar structure and understand its intent without unpacking every detail.
- Development: patterns suggest solutions when a programmer is stuck, and provide a vocabulary for discussing options.
- Review: code reviews become more concrete when reviewer and author share pattern names ("this should be a Composed Method" is more actionable than "this method is too long").
- Documentation: a pattern name in a comment or commit message conveys more than a paragraph of prose.
- Clean-up: patterns guide refactoring — they identify the shape a piece of code should take.
The pattern format
Each pattern in the book follows a consistent structure:
- Name — a memorable handle for the solution.
- Problem — the recurring situation that triggers the pattern.
- Forces — the competing considerations that make the problem non-trivial.
- Solution — the recommended approach, stated as a recipe.
- Discussion — context, trade-offs, and implementation notes.
- Related patterns — the patterns that typically precede or follow this one.
This format allows a reader to skim for the problem and solution while deferring the nuance to the discussion.
Key ideas
- Patterns capture the gap between reusable code (libraries) and reusable wisdom (design judgment).
- Named patterns create shared vocabulary that makes communication faster and more precise.
- The same structural problems recur across application domains; recognising recurrence is the foundation of expertise.
- A pattern format that separates problem, forces, solution, and discussion lets different readers extract different kinds of value.
- Patterns accelerate learning: a beginner with the catalogue can skip years of expensive trial and error.
- The pattern language in this book is deliberately narrow (coding, not architecture) so that it can achieve practical completeness within its scope.
Key takeaway
Patterns work because they name and package the recurring micro-decisions that experts make unconsciously — turning tacit knowledge into transmissible, discussable, improvable guidance.
Chapter 3 — Behavior
Central question
How should methods be structured, named, and organised, and how should messages be sent between objects to produce code that is both correct and maximally communicative?
Main argument
Chapter 3 is the longest and most pattern-dense chapter in the book, presenting roughly 30 patterns divided across three subsections: Methods, Messages, and Delegation.
Methods — structuring the units of computation
Composed Method: The single most important pattern in the book. Break every method into pieces that each perform one identifiable task and keep all operations at the same level of abstraction. The practical consequence is that most good Smalltalk methods are three to seven lines long. Long methods signal that multiple levels of abstraction are mixed, which forces the reader to hold too many details in working memory simultaneously. Composed Method also naturally produces many small methods with informative names, which drives readability throughout the codebase.
Constructor Method and Constructor Parameter Method: Object creation should use class-side constructor methods (factory methods) that accept all required parameters and return a fully initialised, valid instance. This prevents the creation of half-initialised objects and makes the invariant of the class visible at the call site. The constructor parameter method, typically named setX:y:, sets all instance variables from parameters and is called by the class-side constructor.
Shortcut Constructor Method: For objects frequently created from a literal or simple expression, provide a convenience message sent to the argument. Beck recommends limiting shortcut constructors to three per system to avoid proliferating creation paths. The classic Smalltalk example is 3 @ 4 creating a Point — the @ message sent to the integer 3 creates a Point object from receiver and argument.
Converter Method and Converter Constructor Method: When an object can be converted to another type, provide a method in the source object prefixed with as (e.g., asFloat, asArray). When the target class can accept the source as a constructor argument, provide a constructor that takes the source — this is the Converter Constructor Method. The two patterns cover both directions of the conversion relationship.
Query Method: Return booleans from methods that test a condition, and prefix their selectors with is, was, will, has, or a similar modal verb. This makes the intent clear at the call site: anObject isOpen reads as a question, anObject open is ambiguous between a command and a query.
Comparing Method: Implement < and = for objects that need ordering or equality testing. Smalltalk's <=> (magnitude protocol) requires only < and =; the rest derive. Consistently implement both when implementing either.
Reversing Method: When a computation is expressed as a doSomethingWith: b, sometimes the natural reading is from b's perspective. Provide a method on b that calls the original, reversing the grammatical subject. This is particularly useful when a and b are from different hierarchies and you want to keep the call site idiomatic.
Method Object: When a method has grown so complex — many parameters, many shared temporaries, multiple distinct algorithms — that Composed Method cannot reduce it further, extract the entire method into a new class whose instance variables hold the original method's parameters and temporaries. The single public method compute performs the calculation. This pattern is the escape valve when a method legitimately needs state.
Execute Around Method: When a computation has mandatory setup and teardown steps that bracket a variable body, accept a block and evaluate it between the setup and teardown. The canonical Smalltalk example is file access: file openDo: [:stream | ...] opens the file, evaluates the block with the stream, and closes the file regardless of errors. This pattern ensures the invariant (file always closed) holds even when the body raises an exception.
Debug Printing Method: Implement printOn: for every class to produce a string useful for debugging — one that identifies the object's class and key state. This pattern is low cost but high value: it makes every object self-describing in the debugger and transcript.
Method Comment: Use method comments sparingly. A comment that restates what the code already says adds noise. A comment that explains why — the business rule, the algorithmic choice, the historical context — adds genuine value. The existence of a needed comment is often a signal that the code can be made self-explanatory instead.
Messages — the conversation between objects
Choosing Message: Rather than branching with conditionals (ifTrue:ifFalse:), send a message to the object that should make the choice. Each subclass or variant implements the method differently. This replaces explicit type-testing and conditional logic with polymorphic dispatch. Beck illustrates this with Controller>>controlActivity, whose three messages (controlInitialize, controlLoop, controlTerminate) become choosing messages overridden differently in dozens of controller subclasses.
Decomposing Message: When building up a complex result, send a cascade of messages to self rather than writing a single large expression. Each message performs one step; the decomposition mirrors Composed Method at the message-send level.
Intention Revealing Message: When a method call must make its purpose clear rather than its mechanism, add a method whose sole job is to call the real implementation. The extra method's name reveals the intention; the body delegates to the mechanism. This is the pattern behind most well-named one-line methods.
Intention Revealing Selector: The name of a method should describe what the method accomplishes for the caller, not how it is implemented. sortByDate is a better selector than quicksortDescending. The selector is part of the method's interface; the algorithm is part of its body.
Dispatched Interpretation: When a family of objects must be interpreted differently by a second family of objects, send a message to the first object that includes the name of the second object's class. The first object then calls back to the second. This is the basis for double dispatch.
Double Dispatch: When a computation's result depends on the types of both the receiver and the argument (the classic case is arithmetic between mixed numeric types), implement it by sending a second message from the receiver to the argument, appending the receiver's class name to the selector, and passing the receiver as the argument. Example: Integer>>+ aNumber sends aNumber addInteger: self. Float>>+ aNumber sends aNumber addFloat: self. Both Integer and Float implement addInteger: and addFloat:, so any combination of types routes to exactly one specialised method with no conditionals. This pattern eliminates type-checking conditionals across a cross-product of type combinations.
Mediating Protocol: When two objects that do not know about each other must collaborate, introduce a shared protocol — a set of message names — that both implement. The mediating protocol decouples the two objects while enabling cooperation.
Super: Call super to extend, not replace, inherited behaviour. The key discipline is that super sends must be the first or last thing in a method, not buried in the middle — buried super calls indicate design problems.
Extending Super and Modifying Super: Two specialised patterns for the cases where a subclass adds behaviour before (Extending Super) or substitutes only part of the superclass behaviour (Modifying Super).
Delegation — routing responsibilities
Simple Delegation: When an object holds a component and most of its meaningful behaviour is in that component, forward messages to the component rather than implementing them directly. The delegating object adds no logic; it simply routes.
Self Delegation: When a collaborating object needs to call back to the object that owns it, pass self as an argument so the collaborator can send messages back. This allows the collaborator to operate on the owner without creating a circular reference in the class definition.
Pluggable Behavior: Parameterise an object's behaviour through an instance variable. Three increasing levels of flexibility: store a selector (symbol) and use perform: to invoke it (Pluggable Selector); store a block and evaluate it (Pluggable Block); store a delegate object and send it a fixed message (Collecting Parameter extends this). Pluggable Selector is the simplest; Pluggable Block adds the ability to close over surrounding context; delegate objects provide the full power of polymorphism.
Collecting Parameter: When a method must accumulate results across many recursive or iterative calls, pass a collection as a parameter and add results to it inside each invocation. This avoids the awkwardness of returning collections from recursive calls.
Key ideas
- Composed Method is the keystone pattern: almost all other patterns serve or assume it.
- Method length is a proxy for abstraction consistency — short methods indicate that each method operates at one level.
- Names are not decoration; the selector is the interface, and every misleading name is a bug in the contract.
- Polymorphic dispatch (Choosing Message) eliminates conditionals by encoding type-sensitivity into the class hierarchy.
- Double Dispatch eliminates type-checking across a cross-product of two hierarchies without any explicit conditionals.
- Method Object provides an escape hatch when a single method legitimately needs its own state.
- Execute Around Method guarantees setup/teardown invariants even in the presence of exceptions.
- Pluggable Behavior offers a spectrum from rigid (hardcoded) to flexible (delegate object) — choose the minimum flexibility needed.
Key takeaway
Behavior patterns are fundamentally about making the intent of code visible at every level: in method names, in message sends, in the structure of methods — so that a reader can understand a piece of code as quickly as a writer composed it.
Chapter 4 — State
Central question
How should objects represent and manage their state — through instance variables and temporary variables — in a way that is clear, flexible, and maintainable?
Main argument
State is what distinguishes one instance of a class from another. The decisions about which state to hold, how to initialise it, and how to expose it through methods have large downstream effects on readability and changeability. Chapter 4 presents roughly 20 patterns covering these decisions.
Instance variables — modelling what an object is
Common State: Declare instance variables for every attribute that all instances of the class share. List variables in order of importance — the declaration order is the first signal a reader gets about what the class models. Instance variable names should be in the Role Suggesting Instance Variable Name form: name them for the role they play in the computation, not for their type.
Variable State: When different instances need different attributes — not all instances have the same set of properties — store the varying attributes in a Dictionary keyed by attribute name, held in a single instance variable (conventionally properties). This avoids proliferating instance variables for optional attributes.
Initialization strategies — two philosophies
Explicit Initialization: Override initialize to set every instance variable to its default value immediately after creation. This makes the object's initial state fully visible and auditable. Beck recommends this for simpler classes where the cost of always computing defaults is acceptable.
Lazy Initialization: Implement the getter method to check whether the variable is nil, and if so, set it to its default value before returning it. Example: color ^color isNil ifTrue: [color := Color default]. ^color. Lazy initialization defers work until it is needed, makes default-value computation subclassable (subclasses override defaultColor), and handles cases where initialisation order matters.
Default Value Method: Encapsulate the default value in a separate method prefixed with default (e.g., defaultColor). This makes the default explicit, documentable, and overridable without duplicating the logic.
Constant Method: For values that never change and are referenced in multiple methods, return them from a named method rather than using literals inline. maximumSize ^256 is more readable and easier to change than 256 scattered through the code.
Accessing instance variables — direct vs. indirect
Direct Variable Access: Access instance variables directly by name within the class that defines them. This is the simplest approach and appropriate for small, cohesive classes where all methods are in one place.
Indirect Variable Access: Access instance variables exclusively through getter and setter methods, even within the class itself. This provides a single point of change if the storage representation ever needs to evolve and enables subclasses to override access behaviour.
Getting Method and Setting Method: Simple accessors named after the variable (color and color:). Beck argues that getters should return the variable value directly and setters should set it directly in most cases. Keeping accessors thin prevents logic from hiding in unexpected places.
Collection Accessor Method: When an instance variable holds a collection, do not expose the collection directly. Instead, provide methods that combine the collection name with collection messages: addTask: aTask, removeTask: aTask, tasks (returning a copy). This encapsulates the collection and prevents callers from bypassing the object's invariants by modifying the collection directly.
Enumeration Method: Provide a method that takes a block and iterates over the collection, rather than exposing the collection for external iteration. tasks do: [:t | ...] in the caller, tasksDo: aBlock ^tasks do: aBlock in the class.
Boolean Property Setting Method: For boolean state, provide two separate one-argument setters rather than a single setFlag: aBoolean: beOpen and beClosed. This reads more naturally at the call site and makes it impossible to pass an incorrect type.
Temporary variables — naming the ephemeral
Temporary Variable: Declare a temporary variable when a value is needed across multiple statements within a method but does not belong in any instance variable.
Collecting Temporary Variable: When a method accumulates results through a loop, declare a collection before the loop (result := OrderedCollection new) and add to it inside the loop. Return the collection at the end.
Caching Temporary Variable: When an expensive computation's result is used more than once in a method, compute it once into a temporary variable and reference the variable subsequently.
Explaining Temporary Variable: Extract a complex sub-expression into a named temporary variable solely to give it a readable name. average := total / count is clearer at the point of use than the full expression repeated.
Reusing Temporary Variable: When a method executes an expression whose value may change between invocations (e.g., a method that modifies state), capture the result once and reuse the variable rather than calling the expression twice.
Role Suggesting Temporary Variable Name: Name temporaries for the role they play, not their type. result, index, average, eachTask — not temp1, x, anObject.
Key ideas
- Instance variable declarations are the class's self-description; their order and names communicate the model.
- Explicit and lazy initialization are genuine trade-offs, not right/wrong choices — the decision depends on whether default computation is expensive and whether subclassability of defaults matters.
- Direct vs. indirect variable access is another genuine trade-off: directness is simpler; indirectness buys future flexibility at the cost of indirection.
- Collection accessors protect invariants by preventing callers from mutating internal state directly.
- Temporary variable names are as important as method names — they are the reader's guide through a method's local logic.
- The Default Value Method pattern makes defaults explicit, documentable, and subclassable — three properties that inline literals cannot provide.
Key takeaway
State patterns make an object's internal model visible and safe: visible through well-named variables and initialisation methods, safe through disciplined access patterns that protect invariants.
Chapter 5 — Collections
Central question
Which collection class should be chosen for a given situation, which collection messages should be used, and what idiomatic patterns solve common collection-manipulation problems?
Main argument
Collections are among the most-used objects in any Smalltalk program, and the choice of collection class and message has large effects on both performance and readability. Chapter 5 presents roughly 27 patterns organised into three groups: Classes, Protocol, and Idioms.
Collection classes — choosing the right container
Collection (abstract root): Beck describes the protocol shared by all collections before treating the concrete classes.
-
OrderedCollection: The workhorse. Use it when you need a growable, ordered collection. Supports
addFirst:,addLast:,removeFirst,removeLast, iteration. Analogous to a list or deque. - RunArray: Efficient storage for collections where adjacent elements are equal. Rarely needed, but indispensable for its use case (e.g., text with runs of the same font).
-
Set: When uniqueness matters and order does not.
Setperforms the duplicate-detection work so you do not have to. Requires=andhashto be correctly implemented on elements. -
Equality Method and Hashing Method: If objects will live in Sets or be keys in Dictionaries,
=andhashmust be implemented consistently: objects that are=must return the samehash. Beck gives the implementation contract clearly — failing to maintain this invariant causes objects to be "lost" in hashed collections. -
Dictionary: Key-value storage. Use it for sparse or dynamic keying where array indexing is too rigid. The key patterns are
at:,at:ifAbsent:,at:put:, anddo:. -
SortedCollection: An
OrderedCollectionthat maintains sorted order automatically. Accepts a sort block. Use it when sorted order must be maintained incrementally. -
Array: Fixed-size, indexed storage. Use when size is known at creation time and random access by index matters.
ByteArrayis the specialised form for byte-valued elements. -
Interval: A range of integers with step. Use
1 to: 10instead of anArrayof integers — theIntervalis a lightweight, non-materialised representation.
Collection protocol — the standard messages
-
IsEmpty: Test emptiness with
isEmptyandnotEmptyrather than comparingsize = 0. More expressive and occasionally more efficient. -
Includes:: Test membership with
includes:rather than manual iteration. -
Concatenation: Use
,to concatenate collections. Be aware that it creates a new collection. -
Enumeration patterns: Beck treats the five enumeration messages as the core of idiomatic Smalltalk:
-
Do:
do:for side-effecting iteration — visit each element for its effect. -
Collect:
collect:to transform a collection — produce a new collection by applying a block to each element. -
Select/Reject:
select:andreject:to filter — produce a new collection of elements satisfying (or not satisfying) a predicate block. -
Detect:
detect:to find the first matching element. Usedetect:ifNone:when absence is possible. -
Inject:into::
inject:into:to fold — accumulate a single value across the collection. The Smalltalk name forreduceorfold. Example:#(1 2 3 4) inject: 0 into: [:sum :each | sum + each]computes the sum.
-
Do:
Collection idioms — standard tricks
-
Duplicate Removing Set: Wrap a collection in a Set to remove duplicates:
aCollection asSet. -
Temporarily Sorted Collection: Sort for a single operation without changing the underlying collection:
aCollection asSortedCollection. -
Stack: Use an
OrderedCollectionaccessed only viaaddLast:/removeLastfor LIFO behaviour. -
Queue: Use an
OrderedCollectionaccessed only viaaddLast:/removeFirstfor FIFO behaviour. -
Searching Literal: Use an
Arrayof literals as a lookup table rather than a chain of conditionals. -
Lookup Cache: Store computed results in a
Dictionaryto avoid recomputation. The dictionary maps inputs to outputs. -
Parsing Stream: Use a
ReadStreamover a collection to parse it position by position.ReadStream on: aCollectionallowsnext,peek,upToEnd, and positional navigation. -
Concatenating Stream: Use a
WriteStreamto build a collection incrementally without creating many intermediate objects.WriteStream on: String newis the idiomatic way to assemble strings.
Key ideas
- Choosing the right collection class is a design decision, not a clerical one — the class signals the intended access pattern and invariants to every reader.
-
=andhashform a contract; violating it silently corrupts Set and Dictionary behaviour. - The five enumeration messages (
do:,collect:,select:,detect:,inject:into:) replace most loop constructs in idiomatic Smalltalk and should be preferred wherever they fit. - Streams (ReadStream, WriteStream) are the idiomatic way to parse and construct collections without intermediate allocations.
- Collection idioms encode accumulated experience — the Stack/Queue/Lookup Cache patterns show how to reuse simple collection classes for more complex data structure needs.
Key takeaway
Collections are central to Smalltalk programming; selecting the right class and using the standard enumeration protocol produces code that is both idiomatic and efficient.
Chapter 6 — Classes
Central question
How should classes be named to communicate their role and relationship within the system?
Main argument
Class names are the highest-level vocabulary in a Smalltalk system. Because Smalltalk uses a single, flat namespace for all classes, names must be chosen carefully to avoid collisions and to communicate structure. Beck presents only two patterns in this chapter, but they carry significant weight.
Simple Superclass Name: Name root classes (classes with no meaningful superclass in the application domain) with a single, simple noun: Account, Report, Document. The name should describe what the class is, not what it inherits from or what layer it lives in. Avoid suffixes that reflect implementation concerns (AccountManager, ReportHelper) — these signal that responsibilities have been misallocated.
Qualified Subclass Name: Name subclasses by qualifying the superclass name with a distinguishing adjective or noun: SavingsAccount, CheckingAccount, AnnualReport. The superclass name is the genus; the qualifier is the differentia. This naming scheme makes the class hierarchy readable at a glance — a reader immediately knows that SavingsAccount is a kind of Account. Avoid names that diverge from this pattern (like naming a subclass after its role in a different layer) because they obscure the inheritance relationship.
These two patterns together create a self-documenting class hierarchy: names encode the taxonomy of the domain model.
Key ideas
- Class names are the primary vocabulary of the system; investing in good names pays dividends every time code is read.
- Simple superclass names describe what, not how — they anchor the domain model.
- Qualified subclass names preserve the inheritance relationship in the name, making the hierarchy navigable without consulting the class definition.
- Names that break the pattern (using implementation-layer suffixes, or names that don't reflect the superclass) signal a design smell — usually a misplaced responsibility.
Key takeaway
Class names encode the domain taxonomy; a simple root name and qualified subclass names make the inheritance hierarchy readable from the namespace alone.
Chapter 7 — Formatting
Central question
How should Smalltalk code be physically laid out — method signatures, parameter names, control flow indentation, block shape — to maximise readability?
Main argument
Beck opens the chapter with the observation that formatting generates more controversy than almost any other coding topic, yet the stakes are real: consistent formatting reduces cognitive load for readers and reviewers. He codifies ten patterns from his own experience.
Inline Message Pattern: Format a method definition with the message pattern on one line, arguments aligned. For keyword messages with multiple arguments, put each argument on the same line if they fit; if not, align the colons.
Type Suggesting Parameter Name: Name method parameters to suggest the expected type: aColor, anInteger, aPoint, aCollection. The a/an prefix is the Smalltalk convention for a single instance; some prefix is used for collections. This gives a reader a strong hint about what kind of object to pass without looking at the body.
Indented Control Flow: Indent the bodies of ifTrue:, ifFalse:, whileTrue:, timesRepeat: blocks by one level. Keep the message keyword (ifTrue:) and the bracket on the same line: condition ifTrue: [body] for one-liners; multi-line bodies use the bracket on the same line as the keyword with the body indented below.
Rectangular Block: Format multi-line blocks with the opening bracket at the end of the message keyword line and the closing bracket on its own line at the same indentation as the opening line. This creates a rectangular visual "box" for each block body that makes nesting depth immediately visible.
Guard Clause: When a method has a precondition, check and return early rather than nesting the main body inside a conditional. Replace condition ifTrue: [main body] with condition ifFalse: [^self] followed by the unnested main body. Guard Clause reduces nesting and makes the precondition explicit at the top of the method.
Conditional Expression: When the result of a conditional is needed as a value (not for side effects), use ifTrue:ifFalse: as an expression: value := condition ifTrue: [x] ifFalse: [y]. Avoid assigning inside conditional branches when the assignment can be factored out.
Simple Enumeration Parameter: When a block passed to an enumeration method needs a name for its parameter, use a short, expressive name that describes the element: [:each | ...], [:task | ...], [:account | ...]. Avoid generic names like x or element when a domain-specific name fits.
Cascade: Send multiple messages to the same receiver using the cascade operator (;). Use cascade when a series of messages all go to the same object and none of the results are needed. The canonical use case is building an object through a series of configuration messages. Avoid cascades where each message's return value matters.
Yourself: When the last message in a cascade is one whose return value the cascade would otherwise discard, append yourself to ensure the cascade expression evaluates to the receiver rather than the last message's return value. This is most often needed when the receiver is a newly created object being configured.
Interesting Return Value: Return self from methods that modify the receiver (command methods) only when the return value would genuinely help callers chain messages. Most command methods should return self implicitly (Smalltalk's default); explicit ^self is a signal that the return value is intentional. Methods that compute a value (query methods) should always return that value explicitly.
Key ideas
- Formatting is not aesthetic preference — it encodes information about structure, type, and control flow that readers use constantly.
- Guard Clause reduces nesting and makes preconditions visible; deeply nested conditionals hide the main flow.
- Type Suggesting Parameter Names give readers type hints at call sites without annotation overhead.
- Cascade and Yourself work as a pair: cascade chains messages to one receiver; yourself preserves the receiver as the expression value.
- Consistent formatting reduces the cognitive effort of code review by eliminating surprises.
Key takeaway
Formatting is the last mile of communication — consistent conventions for layout, naming, and control-flow reduce the cognitive load on every reader of the code.
Chapter 8 — Development Example
Central question
How do the patterns from the preceding chapters work together in practice when building a real piece of code from scratch?
Main argument
The final chapter is a worked case study rather than a pattern catalogue. Beck builds a simple arithmetic system — integer and floating-point numbers that can be added to one another — and shows how the patterns from chapters 3–7 are applied at each step. The example is intentionally small so the reasoning at each decision point can be shown in full.
Problem
The problem posed is: implement an object-oriented arithmetic system where integer and floating-point values can be added together, with the result type determined by the operands' types (integer + integer = integer; integer + float = float; float + float = float).
Start
Beck begins with the simplest possible code and applies Composed Method from the outset. Even for a trivial arithmetic class, he writes the methods as small, named pieces rather than one large method. He applies Intention Revealing Selectors at every step.
Arithmetic — Double Dispatch applied
The central challenge is handling mixed-type arithmetic without conditionals. Beck applies Double Dispatch explicitly:
-
Integer>>+ aNumbersendsaNumber addInteger: self -
Float>>+ aNumbersendsaNumber addFloat: self - Both
IntegerandFloatimplement bothaddInteger:andaddFloat:
When anInteger + aFloat executes, the Integer dispatches to aFloat addInteger: anInteger, which the Float handles by converting the integer and performing floating-point addition. The result is type-correct with no isKindOf: checks and no conditionals. The pattern table has exactly one method per (receiver type, argument type) pair.
Integration — assembling the patterns
Beck shows how State patterns (instance variables for value, initialization patterns), Formatting patterns (guard clause, type-suggesting parameter names), and Collection idioms all interact as the arithmetic system grows. The integration section demonstrates that the patterns are not independent micro-decisions — they compose into a coherent style that is consistently applied across the entire codebase.
Summary
Beck concludes by reflecting on the development: the resulting code is short, uses no conditionals for type dispatch, is easy to extend (adding Fraction or Complex requires implementing the double-dispatch protocol, not modifying existing code), and reads at a single level of abstraction in each method. He argues that this is the payoff of internalising the patterns — not any individual pattern's benefit, but the cumulative effect of consistently applying all of them together.
Key ideas
- The development example demonstrates that patterns compose — applying them consistently produces a qualitatively different codebase, not just incremental improvements.
- Double Dispatch is the payoff pattern for the arithmetic problem: it eliminates type-checking completely and makes the system extensible by adding new classes rather than modifying existing ones.
- Composed Method, Intention Revealing Selector, and Guard Clause are visible at every step — they are the background fabric, not special occasions.
- The example shows that small objects and small methods produce systems that are easy to extend precisely because each class has a clearly bounded responsibility.
- Starting simple and applying patterns as complexity emerges is Beck's recommended development rhythm.
Key takeaway
The development example shows the patterns working as a system: each pattern solves a local problem, but their cumulative application produces code that is correct, readable, and open for extension without modification.
The book's overall argument
- Chapter 1 (Introduction) — establishes that the primary purpose of code is communication to human readers, and that good style is a discipline of small decisions, not just large architectural choices.
- Chapter 2 (Patterns) — argues that patterns are the right vehicle for transmitting the tacit knowledge of experienced programmers: they name recurring problems, capture the forces, and specify solutions in a format that is readable, discussable, and learnable.
- Chapter 3 (Behavior) — delivers the largest body of patterns, governing how methods are structured and messages are sent; Composed Method and Intention Revealing Selector establish the communicative standard; Double Dispatch and Method Object handle the hard cases where naive approaches produce conditionals or tangled state.
- Chapter 4 (State) — addresses how objects hold and expose their state; the key insight is that naming, initialization strategy, and access discipline each have large downstream effects on readability and extensibility.
- Chapter 5 (Collections) — covers the collection library as a domain where pattern application is dense and high-value; choosing the right class and using the standard enumeration protocol is the idiomatic Smalltalk way to eliminate hand-written loops and conditionals.
- Chapter 6 (Classes) — applies the communication philosophy to the highest-level naming decision: class names encode the domain taxonomy and make the inheritance hierarchy self-documenting.
- Chapter 7 (Formatting) — extends the communication philosophy to physical layout; formatting is not decoration but information, and consistent conventions reduce cognitive load.
- Chapter 8 (Development Example) — demonstrates the patterns composing into a unified style; the arithmetic case study shows Double Dispatch eliminating type-checking and the overall pattern vocabulary producing code that is short, clear, and open for extension.
Common misunderstandings
Misunderstanding: The book is only useful for Smalltalk programmers
The patterns are expressed in Smalltalk, but the underlying principles — Composed Method, Intention Revealing Selector, Guard Clause, Double Dispatch, Lazy Initialization — apply directly to Ruby, Python, Java, and any object-oriented language. Experienced developers in many languages have described this as the most useful OOP micro-design book regardless of language background.
Misunderstanding: The patterns are low-level tricks, not design
Beck's patterns operate at the micro-design level (methods, messages, variables), but their cumulative effect is architectural. A codebase where every method is a Composed Method, every selector reveals intention, and every type-sensitive computation uses Double Dispatch is structurally different from one that does not — it has fewer conditionals, flatter call trees, and clearer boundaries.
Misunderstanding: Small methods are always better
Composed Method recommends breaking methods into pieces that each do one thing at one level of abstraction. Beck explicitly notes that if there is no benefit to be gained — if a method has only one level of abstraction — making it smaller just adds management overhead. Smallness is a consequence of abstraction consistency, not a goal in itself.
Misunderstanding: Lazy Initialization is always preferable to Explicit Initialization
Beck presents both as legitimate patterns with different trade-offs. Explicit Initialization is simpler and makes the initial state fully visible. Lazy Initialization is better when defaults are expensive to compute, when initialization order matters, or when subclass overriding of defaults is needed. Neither is unconditionally preferred.
Misunderstanding: The book prescribes a rigid style
The book presents patterns with forces — the competing considerations that make a problem non-trivial — precisely to prevent mechanical application. A pattern is applicable when its forces are present; it is wrong to apply it when they are not. The book explicitly teaches judgment, not rules.
Central paradox / key insight
The central paradox of the book is that the most important property of code — its ability to communicate — is the one most routinely sacrificed for secondary concerns like raw performance or excessive generality.
Beck's key insight is that optimising code for the human reader and optimising it for the machine are, in practice, often the same thing. Short, well-named methods are not just readable — they are easier for a compiler to inline, easier for a profiler to measure, and easier for a programmer to replace with a faster implementation when evidence demands it. The legibility premium has no real performance cost in most programs.
The deeper insight is about where design happens:
"Programs are not designed and then coded. Programs are coded and then, sometimes, designed."
Beck argues that the act of writing code — choosing names, deciding method boundaries, selecting message sends — is design. The patterns are not post-hoc descriptions of good design; they are the vocabulary of design thinking while coding. A programmer who has internalised the patterns is doing design work at the keyboard, not deferring it to a separate phase.
Important concepts
Pattern
A named solution to a recurring problem in a specific context, documenting the problem, the forces in tension, the recommended solution, and the trade-offs. Beck's patterns are micro-scale: they govern individual methods, messages, and variables rather than subsystems or architectures.
Composed Method
The principle of breaking every method into pieces that each perform one identifiable task at a single level of abstraction. The practical result is methods of three to seven lines. The pattern is the foundation from which most other behavior patterns derive their value.
Intention Revealing Selector
A method name (selector) that describes what the method accomplishes for the caller, not how it is implemented. The selector is the contract; the body is the implementation. Together with Composed Method, it is the primary vehicle for code communication.
Double Dispatch
A technique for handling computations that depend on the types of two objects without using explicit conditionals or isKindOf: tests. The receiver sends a second message to the argument, appending the receiver's class name to the selector and passing itself as argument. The argument then dispatches to the appropriate specialised method. Classic use case: mixed-type arithmetic.
Pluggable Behavior
A spectrum of patterns for parameterising an object's behavior: Pluggable Selector (store a symbol, invoke with perform:), Pluggable Block (store a block, evaluate it), or delegate object (store an object, send it a fixed message). The appropriate level of flexibility depends on how much context the behavior needs.
Lazy Initialization
Deferring the computation and assignment of a default value to the first time a getter is called. The getter checks whether the variable is nil; if so, assigns the default; then returns the value. This makes defaults subclassable (via Default Value Method) and defers unnecessary computation.
Execute Around Method
A method that accepts a block, performs setup, evaluates the block, then performs teardown — guaranteeing the teardown even if the block raises an exception. Encapsulates the paired-operations pattern so callers cannot accidentally omit the teardown.
Method Object
A class created to hold the state (parameters and temporary variables) of a single, complex method. The class has one public method (compute) and uses its instance variables where the original method used parameters and temporaries. Used when a method is too complex for Composed Method alone.
Guard Clause
A formatting pattern where a method's preconditions are checked at the top and return early (rather than nesting the main body in a conditional). Guard Clauses reduce nesting depth and make preconditions explicit.
Cascade
The Smalltalk syntax for sending multiple messages to the same receiver in sequence. Eliminates repetition of the receiver in a series of configuration or side-effecting calls. Works with Yourself to return the receiver as the value of the cascade expression.
Collecting Parameter
Passing a collection as a parameter through recursive or iterative calls, adding results to it at each level, and returning or using it at the end. Avoids the awkwardness of returning collections from each recursive call.
Role Suggesting Name
The principle — applied to instance variables, temporary variables, and parameters — that names should describe the role the variable plays in the computation, not its type. result, eachTask, defaultColor are role names; anObject, temp1, theCollection are type names.
References and Web Links
Primary book and edition information
- Beck, Kent. Smalltalk Best Practice Patterns. Prentice Hall, 1996. ISBN 978-0-13-476904-2.
Background and overview
- ACM Digital Library entry with citation metadata
- Goodreads page with reader reviews
- WorldCat library catalog entry
- Stanford SearchWorks catalog entry
Key ideas — Double Dispatch
- Double Dispatch explained with arithmetic example (tonysm.com)
- Pattern: Double Dispatch (University of Warsaw)
Clippings and notes from the book
- Clippings from Smalltalk Best Practice Patterns (yiming.dev)
- Chapter notes and pattern summaries (GitHub Gist)
Chapter-by-chapter reading blog
Additional chapter summaries and study resources
These are secondary summaries and should be used alongside, rather than instead of, the original book.