The Pragmatic Programmer, Twenty Years On: The Habits Outlived the Tools

By Sergey Nosov

16 October 2026

In the 1990s, Andrew Hunt and David Thomas kept giving the same advice to companies whose projects had problems. The preface to their 2019 edition lists some of it: “maybe you should test that before you ship it; why does the code only build on Mary’s machine? Why didn’t anyone ask the users?” They wrote it down, and their notes became The Pragmatic Programmer, published by Addison-Wesley in 1999.

Twenty years later, they rebuilt the book for its 20th Anniversary Edition. The preface likens the result to the Ship of Theseus: about a third of the topics are new, and most of the rest were rewritten in part or in full. It also says what had aged well: “Technology may have changed, but people haven’t. Practices and approaches that were a good idea then remain a good idea now.”

Read in 2026, that judgment holds a second time: the habits outlived the tools. Those habits apply even to two changes since 2019: AI assistants that write your code, and self-spreading worms that poison the packages you depend on.

What the 2019 Edition Changed

The 2019 edition has fifty-three short topics, with one hundred numbered tips set off in the text and summarized on a pull-out card. Each topic stands on its own, so you can read them in any order.

The first two tips, both kept from 1999, set the tone: “Care About Your Craft” and “Think! About Your Work.” The second asks for continuing attention: question your decisions while you make them, every day, and “never run on auto-pilot.” The new preface even invites readers to push back: “Maybe decide that some of the stuff we suggest is wrong.” In 2019, the authors turned that scrutiny on their own book.

The clearest case is DRY, short for “Don’t Repeat Yourself.” Its definition has not changed since 1999: “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.” But the 2019 topic on duplication soon makes an admission:

Let’s get something out of the way up-front. In the first edition of this book we did a poor job of explaining just what we meant by Don’t Repeat Yourself. Many people took it to refer to code only: they thought that DRY means “don’t copy-and-paste lines of source.”

Avoiding copy-and-paste, they continue, is “part of DRY, but it’s a tiny and fairly trivial part. DRY is about the duplication of knowledge, of intent.” Their acid test asks whether a single change forces you to edit several places, in several different formats.

The new edition then shows the same misreading at work in a code review. An online wine shop validates a customer’s age and the quantity ordered. The owner wants both to be numbers greater than zero, so the two validation functions come out identical. A reviewer rejects them as a DRY violation. The authors side with the code: “The code is the same, but the knowledge they represent is different. The two functions validate two separate things that just happen to have the same rules. That’s a coincidence, not a duplication.”

The harmful case runs the other way: one piece of knowledge in several formats. Suppose a thirty-day refund window appears in the order service, in a nightly SQL job, and on the help page. No line of code repeats another, yet one rule lives in three places, and the next change to it has to find all three. As the DRY topic puts it, “It isn’t a question of whether you’ll remember: it’s a question of when you’ll forget.”

The 2019 edition adds a value that sits above DRY: “Good Design Is Easier to Change Than Bad Design.” In the authors’ account, good design adapts to the people who use it, and code adapts by changing. They call this ETC, for Easier to Change. Their claim for it is large: “As far as we can tell, every design principle out there is a special case of ETC.” They present it as a value, not a rule.

Other changes reflect the twenty years in between. The authors rewrote the topics on concurrency and added material on the agile movement they helped launch, on functional programming, and on privacy. New topics cover property-based testing, naming, and security. The Resources appendix went, partly because it could not be kept current. So did one section of the 1999 edition that deserves a second look; I come back to it below.

The Ideas That Lasted

Orthogonality. In the book’s words, “Two or more things are orthogonal if changes in one do not affect any of the others.” In a well-designed system, you can change the user interface without touching the database. The advice is to “Eliminate Effects Between Unrelated Things,” and a later topic gives the ETC reason: decoupled code is easier to change. Distributed systems raise the price of ignoring orthogonality: a change that ripples through three services can force three coordinated deployments.

Tracer bullets. A tracer bullet is a thin slice that runs end to end through the real architecture. The book insists it is production code: “Tracer code is not disposable: you write it for keeps.” Prototypes, it says, are the opposite: disposable code whose value “lies not in the code produced, but in the lessons learned.” The distinction matters more now that an assistant can produce a plausible prototype in minutes. Decide which one you are building before you start: the danger is a prototype that ships as if it were a tracer.

Broken windows. “Don’t Live with Broken Windows” means fixing bad designs, wrong decisions, and poor code as soon as you find them. The book gives a fallback: “If there is insufficient time to fix it properly, then board it up.” Comment out the offending code, it suggests, or show a “Not Implemented” message, or substitute dummy data: anything that stops further damage and shows the problem is in hand.

A deliberate, visible, temporary compromise is not a broken window. A flaw that everyone has stopped noticing is one, like the commented-out block that outlived its reason; I have written about deleting dead code safely.

Automation. In 1999, the book argued for version control everywhere: “Always Use Source Code Control. Always. Even if you are a single-person team on a one-week project.” The 2019 edition keeps that advice and goes further. In its starter kit, build, test, and deployment “are triggered via commits or pushes to version control, and built in a container in the cloud.” A version-control tag specifies what reaches staging or production, and the authors call such low-ceremony releases “true continuous delivery.”

Another tip, “Don’t Use Manual Procedures,” gives the reason to automate: “People just aren’t as repeatable as computers are. Nor should we expect them to be.” Much of what the first edition had to argue for is now the expected baseline. The argument still holds wherever a step is done by hand.

Ownership. Early on, the book treats responsibility as a choice: “Responsibility is something you actively agree to.” When something goes wrong, it says to offer options, not excuses. Its last topic asks you to “Sign Your Work,” so that your name on a piece of code comes to mean quality. The same topic adds that while code must be owned, “it doesn’t have to be owned by an individual.”

Communication. “English is Just Another Programming Language,” the book says, so documents deserve the care you give code: DRY and easy to change. Remote work and AI assistants both raise the stakes. More of the work now travels as written English: to colleagues who are not in the room, and to assistants as instructions. In spec-driven development, for example, a written specification becomes the contract an AI agent builds against.

DRY, orthogonality, broken windows, and tracer bullets each have an entry, with signs of violation and of proper application, in my Software Development Principles series.

Since the 2019 Edition

The Wizard Came Back

The 1999 edition had a section called “Evil Wizards,” about development tools that generated code for you, such as a GUI builder’s skeleton application. Its tip was blunt: “Don’t Use Wizard Code You Don’t Understand.” Yet the authors were not against wizards; the same book taught readers to write their own code generators. Their warning was narrower: “if you do use a wizard, and you don’t understand all the code that it produces, you won’t be in control of your own application. You won’t be able to maintain it, and you’ll be struggling when it comes time to debug.”

They took on the obvious objection, that developers “routinely rely on things they don’t fully understand,” from processor interrupts to library code. They agreed, and drew a line. In their example, a developer named Joe uses a wizard:

And we’d feel the same about wizards if they were simply a set of library calls or standard operating system services that developers could rely on. But they’re not. Wizards generate code that becomes an integral part of Joe’s application. The wizard code is not factored out behind a tidy interface—it is interwoven line by line with functionality that Joe writes. Eventually, it stops being the wizard’s code and starts being Joe’s. And no one should be producing code they don’t fully understand.

The 2019 edition dropped the section. Seven years later, the wizard is back and far more capable: assistants write whole functions and tests, and agents propose entire changes. The core of the 1999 argument still applies. Like the wizard’s output, an assistant’s code is not a library someone else maintains: once you merge it, it is yours. Even the layers you trust without reading have leaks, which is why I argue for learning one layer below the abstractions you use every day.

Four tips, three of them carried over from 1999, read as if they were written for working with an assistant:

None of this argues against the tools. It argues for the position the book took on wizards: use them, read what they produce, and keep the judgment yourself. My book Code You Did Not Write treats the general problem: how to get correct, coherent output from work you do not fully control. That problem looks the same whether a junior developer, a contractor, or an AI agent did the work.

The Patch Became the Attack

The 2019 edition added a security topic, “Stay Safe Out There.” Its Tip 73, “Apply Security Patches Quickly,” covers every connected device, developer and build machines included. It explains why: “the largest data breaches in history (so far) were caused by systems that were behind on their updates.”

That advice treats a security update as the cure. In 2025 and 2026, attackers made poisoned package updates spread on their own. On 23 September 2025, the US Cybersecurity and Infrastructure Security Agency warned that a self-replicating worm called Shai-Hulud had compromised more than 500 npm packages.

On 4 August 2026, Microsoft described a self-propagating npm worm that it calls a Mini Shai-Hulud variant. After stealing a publishing token, the worm takes each package the stolen identity can publish and adds its payload, with a preinstall script to launch it. Then it “increments the patch version, and republishes the modified packages.”

Microsoft adds that packages the worm publishes through an abused release pipeline “can carry valid provenance because the publication originates from a legitimate workflow identity.” Provenance, a signed record of where and how a package was built, is therefore not enough on its own to trust a release.

A poisoned patch release can reach you through any install that resolves a version range afresh. The first defense is a committed lockfile, which pins exact versions; a plain install honors it while it matches package.json. When the lockfile is missing or out of step, npm install resolves afresh; npm ci and pnpm install --frozen-lockfile fail instead.

A waiting period is a second defense, and it works only if someone else catches a poisoned release before the wait ends. The pnpm package manager added minimumReleaseAge in version 10.16 and made one day the default in version 11. Its documentation says that “in most cases, malicious releases are discovered and removed from the registry within an hour.” Unless you set the age yourself, pnpm falls back to a younger release when nothing in range is old enough.

The npm client added min-release-age in version 11.10, in February 2026. By default, GitHub’s Dependabot does not propose a version update until the release is three days old. Any wait can delay the security fix Tip 73 says to apply quickly. Dependabot sides with the fix: its documentation says, “This default cooldown does not apply to security updates.” When the wait blocks npm audit fix, npm’s documentation says, “npm keeps the package at its vulnerable version, warns that the fix was blocked, and exits with a non-zero code.”

So let routine updates age, and give a fix for a vulnerability you are exposed to a quick, explicit path around the wait. Tip 72, “Keep It Simple and Minimize Attack Surfaces,” offers a defense that needs no early warning. Every dependency is attack surface, and so is every install script it runs. Since version 10 of pnpm and version 12 of npm, dependency install scripts run by default only for packages you approve. The cheapest dependency to secure is the one you never needed.

A Practice, Not a Credential

A degree or a certificate records what someone knew when they earned it. The book treats knowledge as something that decays: “Your knowledge and experience are your most important day-to-day professional assets. Unfortunately, they’re expiring assets.” Its answer is a knowledge portfolio, managed like a financial one: invest regularly, diversify, manage risk, buy low and sell high, and review and rebalance. The tip card puts it in four words: “Make learning a habit.”

The portfolio topic is specific about that habit: “The habit is as important as the sums, so plan to use a consistent time and place.” Its sample goals include a new language every year and a technical book each month. A passage from the first edition’s preface, kept in the second, uses the Japanese term kaizen for “continuously making many small improvements.” It turns the idea into a daily instruction: “Every day, work to refine the skills you have and to add new tools to your repertoire.”

That is why I call professionalism in software a practice, not a credential. A practice is what you do on an ordinary day: think about the work, own the result, and keep learning, because the tools will change again. The same practice speaks to the question the AI era raises: if an assistant writes the code, what is left for the programmer? The answer is what the book has taught since 1999: the judgment, the ownership, and the learning.

Reading It in 2026

Further Reading