AI is changing how we write code, but many of the difficult decisions around it are familiar. We still need to understand the problem, agree on the behavior, choose the right trade-offs, and find out what happens when the software meets real users. Faster implementation can carry a mistaken assumption into more of the system before we notice it.
That is why these eight books deserve another look. Their ideas predate today’s coding agents, even where the editions are recent. What follows is my take on applying them to AI-assisted development, with further reading for specific gaps.
Decide what is worth building
An agent can turn a feature request into a convincing prototype before anyone has established that the idea is valuable. A working demo can make the solution feel more certain while the demand for it remains an assumption. That makes it easier to fall into the build trap: shipping more features while the customer or business outcome stays the same.
The book connects product strategy to the decisions teams make every day. Its distinction between outputs and outcomes gives engineers a reason to challenge a request beyond its technical feasibility. What problem are we solving, what should change for the user, and how will we know? Engineers contribute to that evidence through experiments, instrumentation, and feedback from running systems.
For more on discovery, read Continuous Discovery Habits, or Testing Business Ideas for testing business assumptions before committing to a solution.
Agree on what correct means
Once we know what is worth building, we need to agree on its behavior. Natural language leaves room for several plausible interpretations, and an agent can choose one, implement it, and generate tests that repeat the same assumption. Passing those tests tells us little about whether the assumption was right.
This book, published in 2011, offers a practical way to resolve that ambiguity. Product managers, domain experts, and engineers work through concrete examples before that interpretation becomes working code. A request to let customers cancel orders, for example, needs answers about orders that have shipped, partial shipments, and payments already collected. Those cases expose disagreements that a general requirement can hide.
The important examples can then become executable acceptance checks and living documentation. They give an agent a reference for expected behavior when a prompt changes or an implementation is rewritten. Their value comes from agreement with domain experts, independently of the assumptions in the generated code.
In his ten-year retrospective, Gojko Adzic explains why the conversations around examples often proved more valuable than the resulting tests. Use Given-When-Then where it helps; the format alone will not resolve a disagreement.
Read Writing Effective Use Cases for a more structured treatment of user goals, success paths, and exceptions.
Make business meaning explicit
Examples reveal another problem: the same word can mean different things to different teams. A model may know the language of banking or logistics without knowing what “account” or “completed” means inside your organization. If that meaning is wrong, generation can carry the mistake into schemas, APIs, events, and tests.
Domain-driven design gives us a way to discover and preserve those meanings. A ubiquitous language is developed with domain experts, so the terms in the software reflect how the business works. Bounded contexts define where a particular model applies, and context maps make the relationships between models explicit. This matters when an agent reuses code across services: billing and fulfillment may have similar schemas but different rules for what makes an order complete.
The book connects these choices to implementation through business logic and integration patterns. Types, state transitions, and tests can then constrain generated changes, while domain terminology in the code gives the agent context about which rules belong where.
The distinction between core, supporting, and generic subdomains helps decide where careful modeling is worth the effort, while simple business logic can stay simple.
Read Domain Storytelling for discovering knowledge with domain experts, or Domain Modeling Made Functional for expressing rules in types so invalid states are harder to represent.
Choose architectural trade-offs that fit the system
An agent can scaffold a service, its API, and its deployment files without resolving whether the service needs to be separate. Code generation reduces some of the effort of creating a boundary, but the latency, coordination, and failure modes it introduces remain. A design that is easy to generate may still be expensive to operate and change.
This book develops trade-off analysis through the problems of breaking systems apart and putting them back together. Service granularity, coupling, data ownership, orchestration, and distributed transactions are connected decisions. Moving a boundary may make deployment easier while making consistency and failure recovery harder.
That reasoning also matters across repeated agent changes. A locally simpler implementation may remove an intentional boundary or introduce a dependency the team had rejected. Explicit constraints and the reasoning behind them give both agents and reviewers context that the code alone may not reveal.
The examples lean heavily toward distributed architectures, so use them to evaluate whether the benefits justify the added complexity in your system.
Read Just Enough Software Architecture for matching the amount of architecture work to the project’s risks.
Understand your data guarantees
A generated database client may look correct while leaving unanswered which state is authoritative, how stale a read may be, or what should happen when an update arrives twice. These questions affect every cache, event consumer, search index, and model input that depends on the data.
The book connects data models and storage choices to the guarantees an application needs. Its coverage of replication, transactions, consistency, and partial failure helps explain what an API promises and where that promise ends. Schema evolution and batch and stream processing extend the discussion to systems that must change while continuing to serve readers and writers.
This becomes visible when an agent changes a schema and updates all the code it can see. Older consumers, stored events, and derived indexes may still depend on the previous structure. Data contracts make that compatibility requirement explicit, alongside the rules for duplicates, ordering, and freshness that a successful API call cannot establish.
Read Database Internals for storage engines and database machinery, or Streaming Systems when event time, watermarks, and stateful processing dominate the design.
Identify threats and trust boundaries
The security questions extend beyond the code an agent produces. A coding agent may read a README or tool response while also having shell access and credentials. Through prompt injection, untrusted content can influence actions taken with those privileges. The same concern applies when the application itself gives agents access to tools and data.
Shostack’s four-question framework provides a repeatable process: understand the system, identify what can go wrong, decide how to respond, and review the result. Mapping data flows and trust boundaries makes the system concrete, while a method such as STRIDE helps identify threats systematically.
The 2014 book needs to be supplemented with knowledge of these newer attacks, but its method connects them to familiar design questions: which inputs are untrusted, what authority does the agent have, and where are access controls enforced? A prompt can steer the model’s behavior, while enforced permissions limit the available actions even when it follows a malicious instruction.
Read Designing Secure Software for secure implementation practices that complement threat modeling.
Design for production failures
A secure system can still fail under ordinary load or a slow dependency. An agent may wrap a failing API call in a retry loop without accounting for retries already built into the client library. The code can look more resilient while multiplying requests against a struggling dependency.
Release It! connects application design to these production consequences. Its failure patterns explain how blocked threads, mismatched capacity, and cascading failures turn local trouble into system-wide problems. Its stability patterns, including timeouts, circuit breakers, bulkheads, and back pressure, provide ways to contain that trouble.
These patterns require decisions about how much work the system can accept and what happens when it reaches its limits. Runtime visibility and controls let us observe and adjust that behavior, while deployment practices and failure rehearsal test whether recovery actually works.
Pair it with Observability Engineering, Second Edition for understanding system behavior through production telemetry.
Improve the whole delivery flow
Several coding agents can work in parallel while the team still has one review queue. Faster generation can therefore increase work in progress and integration conflicts without getting changes to users sooner.
Accelerate connects delivery performance to technical practices, architecture, management, and culture. Small batches, continuous integration, deployment automation, loosely coupled systems, and effective feedback help teams move changes through the whole process. Throughput and stability need to be considered together, so an improvement in one does not hide a problem in the other.
Evaluate AI adoption by whether changes reach users sooner, how often they fail, and how quickly the service recovers. Commits, generated lines, and suggestion acceptance rates may explain tool usage, but they do not establish an improvement in delivery or product outcomes.
The book predates today’s coding agents, but DORA’s guidance on small batches makes the connection explicit: large generated changes remain difficult to review, test, and integrate.
Read Continuous Delivery for the delivery machinery, or Team Topologies, Second Edition for team boundaries, cognitive load, and interactions that help or hinder the flow of work.
Where to start
Start with the problem your team is struggling with. The map below pairs each main book with two alternatives for going deeper, so you can choose the path that is most relevant to your work.
AI can generate more of the implementation, but it also lets weak assumptions spread through a system much faster. These books remain relevant because they teach us how to reason about everything the generated code depends on, from what to build to how the result behaves in production.
Which software engineering book would you add to this list, and why has AI made it more relevant?
👋 Found this useful? Like and restack it for other software engineers.











