
Loading configuration requires opening a file, choosing an encoding, stripping comments, converting types and applying defaults. Each step fits in a small function. Yet three consumers repeat the sequence and disagree about unknown keys. Code has been fragmented while knowledge remains scattered.
John Ousterhout's A Philosophy of Software Design offers a useful lens: assess design by the work needed to understand and modify it. I recommend the book for questioning interfaces that seem simple because their obligations live in the user's memory. Depth helps discuss a boundary without establishing an ideal line count.
This review considers the second edition, released in July 2021 according to the author's Stanford page. Ousterhout describes changes covering deciding what matters, general-purpose modules and comparisons with Clean Code. This is a recommendation of that specific edition, without claiming it is the latest.
Bring a familiar interface to the reading. For each principle, write what would change about using that interface and what would remain difficult. This evaluates a design proposal rather than replacing one book's rules with another's.
The official second-edition extract revisits three manifestations of complexity: change amplification, cognitive load and unknown unknowns. They offer more useful questions than counting methods.
Changing comment syntax in our loader might require editing every consumer: amplification. Using the library requires remembering steps, defaults and exceptions: cognitive load. An unnoticed fourth consumer applies a hidden environment variable: relevant information you did not know to investigate.
These symptoms can coexist. Extracting port conversion into a function helps local readability but cannot remove duplicated knowledge about when to validate or which error to present. Follow a complete task from caller to result, including global configuration and initialization order.
Depth relates interface simplicity to the work concealed behind it. A configuration operation can hide reading, parsing and validation while returning a meaningful result. A short function forwarding arguments to another equally difficult function may add a name without removing required knowledge.
That does not justify one function doing everything in the application. Configuration, authentication and connection setup involve different decisions. A useful boundary collects related knowledge and offers an understandable contract. Internal size belongs alongside cohesion, maintenance and failure verification in the evaluation.
Count invisible requirements as interface too. A minimal load() signature may depend on the working directory and undocumented globals. An explicit load_settings(path) can demand less knowledge. Parameter count alone incompletely describes the cost of using an operation.
This hypothetical exercise includes no executed implementation. An application uses a UTF-8 file containing key=value pairs. It recognizes two keys: port, an integer from 1 through 65535, and cache, accepting exactly true or false. Blank lines and standalone comment lines beginning with # are ignored.
Reject unknown keys, duplicates and malformed lines. An absent key in a valid file receives port=8080 or cache=false. A nonexistent file is not empty configuration: return a read error. This avoids silently starting with defaults after someone mistypes a path.
Callers supply an absolute path; relative paths produce INVALID_PATH. Application startup chooses the location before invoking the loader. Changing the working directory therefore cannot silently select a different configuration file. This decision belongs to our exercise contract rather than a universal requirement for configuration libraries. It makes the source-selection responsibility explicit instead of burying it inside an apparently convenient signature.
A valid input is:
#local development
port=9000
cache=true
The expected result is immutable configuration containing integer port 9000 and boolean true for cache. Strings 9000 and true do not leak to consumers for interpretation. The module closes the file, preserves the working directory and changes neither the environment nor the source file.
| Input or condition | Contract result |
|---|---|
| Valid file above | Port 9000, cache true |
| Existing empty file | Port 8080, cache false |
port=0 on line 1 | INVALID_VALUE, key port, line 1 |
Two cache definitions | DUPLICATE_KEY at the second definition |
| Nonexistent path | READ_ERROR, no default configuration returned |
For this exercise, return valid configuration or a structured error, never a partially validated configuration. Errors identify category, path and line when a line is relevant. The caller decides whether startup stops and how to present the issue; the loader prints nothing and never terminates the process.
Free and paid products to turn ideas into a foundation you can actually ship.
13 products availableContinue exploring similar topics

Read the classic catalog through a concrete change and choose abstractions by the costs they remove.

Apply Michael Nygard's approach to timeouts, isolation and recovery through a controlled failure exercise.

Turn passive study into a learning project with direct practice, feedback and spaced retrieval.
A 47-point checklist to find bugs, security risks, and performance issues before launch.
Production-tested templates trusted by developers. Save weeks of setup on your next project.
In the first interface, consumers coordinate reading lines, removing comments, splitting pairs, converting values, supplying defaults and validating. Correct sequencing requires knowledge of format and policy at every stage. Changing comments forces inspection of uses that should merely obtain settings.
With the alternative, a caller supplies an explicit path and handles load_settings(path)'s result. It knows available options and error categories without reproducing parsing. The module owns grammar and conversion. Knowledge has moved behind a boundary rather than disappearing from the system.
For example, changing the default port from 8080 to 8081 should require one loader decision and updated contract tests. If consumers still apply individual fallbacks, policy remains duplicated. Improvement must appear during the next change instead of being assumed from centralization alone.
Hiding parsing does not mean hiding disk access or possible failure. Callers need to know that they receive a snapshot; this contract provides no automatic reload. If another routine later edits the file, previously returned configuration stays unchanged.
For reload requirements, design another operation with a failure policy: retain the last valid snapshot or stop serving? Silently adding a watcher to the original function changes effects and responsibilities without enlarging the signature. That would be false simplification.
Avoid turning the module into a framework for imagined possibilities. Multiple formats, validation plugins and precedence across sources increase what someone must understand. Introduce those options when concrete uses fail to fit the existing contract.
A useful comment explains why a nonexistent file fails while a missing key receives a default. The reason distinguishes deployment error from intentional omission. Without it, a later simplification might turn a visible failure into incorrect startup.
Document units, resource ownership and snapshot stability too. Saying "converts port to integer" repeats an operation without explaining the limit or the error clients receive. Contract comments should support using an interface without reading its entire implementation.
Those explanations need review as well. After behavior changes, stale comments may create misleading confidence. Write verifiable contract examples and review explanations alongside code. Prose complements implementation without becoming evidence of correctness.
Distinguishing tactical from strategic work suggests investing in the next change alongside completing this one. For our loader, that might mean collecting duplicated policy and specifying the table's cases before adding a third option. The investment is local and connected to an existing difficulty.
Do not use the idea to justify an unlimited rewrite. Compare two small interfaces, choose one and preserve compatibility during adoption. A temporary adapter can migrate consumers separately; decide when to remove it rather than retaining two policies indefinitely.
Observe the return. Ask someone to add an option using only the written contract. Record questions and edited locations. Those provide specific evidence about this design without creating a universal productivity promise or an invented improvement percentage.
In Ousterhout's published discussion with Robert Martin, they debate method length, comments and TDD. Ousterhout challenges decomposition that increases required knowledge. Martin favors short functions while acknowledging excessive decomposition is possible.
Do not reduce this to small functions against large ones. A clearly named internal port-validation helper may be useful. Trouble arises when every consumer must coordinate helpers whose separation conceals no decisions. Evaluate calls and contracts before selecting a preferred size.
This compares design criteria rather than permitting unreadable code. A long method mixing reading, policy and effects without structure can be difficult too. Ask which information you can stop considering while using or modifying the module.
My main reservation is the subjectivity of simplicity and depth. Authors familiar with an implementation may underestimate its cost for others. Reviews with different readers and concrete changes can challenge that perception. A centralized design can also become a maintenance bottleneck when it collects unrelated policies.
Hiding complexity does not necessarily lower internal risk. The loader still needs encoding, boundary and resource-closure tests. A pleasant interface can conceal a fragile implementation. The book offers discussion criteria without replacing tests, security knowledge or execution-environment analysis.
I recommend it to developers maintaining modules consumed elsewhere who have watched a change spread. Without that experience, practice with the loader before generalizing the principles. For a short, disposable task, an extensible API may cost more than solving the immediate problem.
Clean Code complements local readability decisions. Clean Architecture helps discuss dependencies between parts. Fowler's Refactoring provides moves for migrating existing structure. Ousterhout adds evaluation of the knowledge those boundaries demand.
Read complexity, depth and information hiding alongside a real interface. Write the caller's minimum manual, compare two alternatives and try one change in both. Return to comments and strategy to record decisions and plan adoption. This keeps reflective reading from becoming a list of prohibitions.
I recommend the book for asking better review questions. You have used it well when you can show which decision stopped leaking to consumers and which consequences remain explicit. A smaller signature deserves credit only when it reduces the work of using and modifying the system.