My Key Takeaways from The Pragmatic Programmer: Part I

Alfredo Bautista Santos
14 min readJan 28, 2025

--

Hi everyone! I’m Alfredo Bautista, a Flutter & Dart GDE and frontend software developer. I recently finished reading the incredible book “The Pragmatic Programmer,” and I’m so eager to share some of its most valuable tips and insights with all of you.

This book is a must-read, and my goal with this post is to highlight the key takeaways that resonated most with me, in the hope that they’ll be just as valuable for you on your own development journey.

You try to capture elusive requirements and find a way of expressing them so that a mere machine can do them justice.

Programming is a craft. At its simplest, it comes down to getting a computer to do what you want it to do (or what your user wants it to do). As a programmer, you are part listener, part advisor, part interpreter, and part dictator. You try to capture elusive requirements and find a way of expressing them so that a mere machine can do them justice. You try to document your work so that others can understand it, and you try to engineer your work so that others can build on it. What’s more, you try to do all this against the relentless ticking of the project clock.

From the Preface to the First Edition, página 26

We who cut mere stones must always be envisioning cathedrals.

Think about the large cathedrals built in Europe during the Middle Ages. Each took thousands of person-years of effort, spread over many decades. Lessons learned were passed down to the next set of builders, who advanced the state of structural engineering with their accomplishments. But the carpenters, stonecutters, carvers, and glass workers were all craftspeople, interpreting the engineering requirements to produce a whole that transcended the purely mechanical side of the construction. It was their belief in their individual contributions that sustained the projects: We who cut mere stones must always be envisioning cathedrals.

From the Preface to the First Edition, página 32

Software Entropy

Being responsible, Pragmatic Programmers won’t sit idly by and watch their projects fall apart through neglect. In Topic 3, Software Entropy, we tell you how to keep your projects pristine.

Chapter 1. A Pragmatic Philosophy, página 34

Stone Soup and Boiled Frogs

Most people find change difficult, sometimes for good reasons, sometimes because of plain old inertia. In Topic 4, Stone Soup and Boiled Frogs, we look at a strategy for instigating change and (in the interests of balance) present the cautionary tale of an amphibian that ignored the dangers of gradual change.

Chapter 1. A Pragmatic Philosophy, página 35

Good-Enough Software

One of the benefits of understanding the context in which you work is that it becomes easier to know just how good your software has to be. Sometimes near-perfection is the only option, but often there are trade-offs involved. We explore this in Topic 5, Good-Enough Software.

Chapter 1. A Pragmatic Philosophy, página 35

Your Knowledge Portfolio

Of course, you need to have a broad base of knowledge and experience to pull all of this off. Learning is a continuous and ongoing process. In Topic 6, Your Knowledge Portfolio, we discuss some strategies for keeping the momentum up.

Chapter 1. A Pragmatic Philosophy, página 35

Communicate!

Finally, none of us works in a vacuum. We all spend a large amount of time interacting with others. Topic 7, Communicate! lists ways we can do this better.

Chapter 1. A Pragmatic Philosophy, página 35

Provide Options, Don’t Make Lame Excuses

Telling your boss “the cat ate my source code” just won’t cut it. Tip 4 Provide Options, Don’t Make Lame Excuses

Chapter 1. A Pragmatic Philosophy, página 40

Don’t Live with Broken Windows

Ignoring a clearly broken situation reinforces the ideas that perhaps nothing can be fixed, that no one cares, all is doomed; all negative thoughts which can spread among team members, creating a vicious spiral. Tip 5 Don’t Live with Broken Windows

Chapter 1. A Pragmatic Philosophy, página 43

Be a Catalyst for Change

People find it easier to join an ongoing success. Show them a glimpse of the future and you’ll get them to rally around. Tip 6 Be a Catalyst for Change

Chapter 1. A Pragmatic Philosophy, página 47

Remember the Big Picture

It’s often the accumulation of small things that breaks morale and teams. Tip 7 Remember the Big Picture

Chapter 1. A Pragmatic Philosophy, página 48

Make Quality a Requirements Issue

The scope and quality of the system you produce should be discussed as part of that system’s requirements. Tip 8 Make Quality a Requirements Issue

Chapter 1. A Pragmatic Philosophy, página 51

Invest Regularly in Your Knowledge Portfolio

Of all these guidelines, the most important one is the simplest to do: Tip 9 Invest Regularly in Your Knowledge Portfolio

Chapter 1. A Pragmatic Philosophy, página 57

Critically Analyze What You Read and Hear

Just because a bookstore features a book prominently doesn’t mean it’s a good book, or even popular; they may have been paid to place it there. Tip 10 Critically Analyze What You Read and Hear

Chapter 1. A Pragmatic Philosophy, página 60

Having the best ideas, the finest code, or the most pragmatic thinking is ultimately sterile unless you can communicate with other people.

Maybe we can learn a lesson from Ms. West. It’s not just what you’ve got, but also how you package it. Having the best ideas, the finest code, or the most pragmatic thinking is ultimately sterile unless you can communicate with other people. A good idea is an orphan without effective communication.

Chapter 1. A Pragmatic Philosophy, página 63

you need to understand the needs, interests, and capabilities of your audience.

You’re communicating only if you’re conveying what you mean to convey-just talking isn’t enough. To do that, you need to understand the needs, interests, and capabilities of your audience. We’ve all sat in meetings where a development geek glazes over the eyes of the vice president of marketing with a long monologue on the merits of some arcane technology. This isn’t communicating: it’s just talking, and it’s annoying.

Chapter 1. A Pragmatic Philosophy, página 64

It’s Both What You Say and the Way You Say It

Tip 12 It’s Both What You Say and the Way You Say It

Chapter 1. A Pragmatic Philosophy, página 68

Build Documentation In, Don’t Bolt It On

In fact, we want to apply all of our pragmatic principles to documentation as well as to code. Tip 13 Build Documentation In, Don’t Bolt It On

Chapter 1. A Pragmatic Philosophy, página 68

The Essence of Good Design

The first and maybe most important topic gets to the heart of software development: Topic 8, The Essence of Good Design. Everything follows from this.

Chapter 2. A Pragmatic Approach, página 73

DRY — The Evils of Duplication

The next two sections, Topic 9, DRY — The Evils of Duplication and Topic 10, Orthogonality, are closely related. The first warns you not to duplicate knowledge throughout your systems, the second not to split any one piece of knowledge across multiple system components.

Chapter 2. A Pragmatic Approach, página 73

Good Design Is Easier to Change Than Bad Design

First, the general statement: Tip 14 Good Design Is Easier to Change Than Bad Design

Chapter 2. A Pragmatic Approach, página 75

DRY — Don’t Repeat Yourself

Why do we call it DRY? Tip 15 DRY — Don’t Repeat Yourself

Chapter 2. A Pragmatic Approach, página 80

DRY is about the duplication of knowledge, of intent. It’s about expressing the same thing in two different places, possibly in two totally different ways.

That is part of DRY, but it’s a tiny and fairly trivial part. DRY is about the duplication of knowledge, of intent. It’s about expressing the same thing in two different places, possibly in two totally different ways.

Chapter 2. A Pragmatic Approach, página 81

Make It Easy to Reuse

Tip 16 Make It Easy to Reuse

Chapter 2. A Pragmatic Approach, página 90

Eliminate Effects Between Unrelated Things

When components of any system are highly interdependent, there is no such thing as a local fix. Tip 17 Eliminate Effects Between Unrelated Things

Chapter 2. A Pragmatic Approach, página 94

ask yourself how decoupled your design is from changes in the real world.

Also ask yourself how decoupled your design is from changes in the real world. Are you using a telephone number as a customer identifier? What happens when the phone company reassigns area codes? Postal codes, Social Security Numbers or government IDs, email addresses, and domains are all external identifiers that you have no control over, and could change at any time for any reason. Don’t rely on the properties of things you can’t control.

Chapter 2. A Pragmatic Approach, página 97

Write shy code — modules that don’t reveal anything unnecessary to other modules and that don’t rely on other modules’ implementations.

Keep your code decoupled. Write shy code — modules that don’t reveal anything unnecessary to other modules and that don’t rely on other modules’ implementations. Try the Law of Demeter, which we discuss in Topic 28, Decoupling. If you need to change an object’s state, get the object to do it for you. This way your code remains isolated from the other code’s implementation and increases the chances

Chapter 2. A Pragmatic Approach, página 98

Duplicate code is a symptom of structural problems.

Often you’ll come across a set of functions that all look similar — maybe they share common code at the start and end, but each has a different central algorithm. Duplicate code is a symptom of structural problems. Have a look at the Strategy pattern in Design Patterns for a better implementation.

Chapter 2. A Pragmatic Approach, página 99

Orthogonality is closely related to the DRY principle.

With DRY, you’re looking to minimize duplication within a system, whereas with orthogonality you reduce the interdependency among the system’s components. It may be a clumsy word, but if you use the principle of orthogonality, combined closely with the DRY principle, you’ll find that the systems you develop are more flexible, more understandable, and easier to debug, test, and maintain.

Chapter 2, A Pragmatic Approach, página 100

The project probably is not orthogonally designed and coded.

If you’re brought into a project where people are desperately struggling to make changes, and where every change seems to cause four other things to go wrong, remember the nightmare with the helicopter. The project probably is not orthogonally designed and coded. It’s time to refactor.

Chapter 2, A Pragmatic Approach, página 100

Reversibility

Engineers prefer simple, singular solutions to problems. Math tests that allow you to proclaim with great confidence that are much more comfortable than fuzzy, warm essays about the myriad causes of the French Revolution. Management tends to agree with the engineers: singular, easy answers fit nicely on spreadsheets and project plans.

Chapter 2, A Pragmatic Approach, página 104

The mistake lies in assuming that any decision is cast in stone — and in not preparing for the contingencies that might arise.

Instead of carving decisions in stone, think of them more as being written in the sand at the beach. A big wave can come along and wipe them out at any time.

Chapter 2, A Pragmatic Approach, página 106

There Are No Final Decisions

The mistake lies in assuming that any decision is cast in stone — and in not preparing for the contingencies that might arise. Instead of carving decisions in stone, think of them more as being written in the sand at the beach. A big wave can come along and wipe them out at any time. Tip 18 There Are No Final Decisions

Chapter 2, A Pragmatic Approach, página 106

Look for the important requirements, the ones that define the system. Look for the areas where you have doubts, and where you see the biggest risks. Then prioritize your development so that these are the first areas you code.

In fact, given the complexity of today’s project setup, with swarms of external dependencies and tools, tracer bullets become even more important. For us, the very first tracer bullet is simply create the project, add a “hello world!,” and make sure it compiles and runs. Then we look for areas of uncertainty in the overall application and add the skeleton needed to make it work.

Chapter 2, A Pragmatic Approach, página 111

Use Tracer Bullets to Find the Target

Look for the important requirements, the ones that define the system. Look for the areas where you have doubts, and where you see the biggest risks. Then prioritize your development so that these are the first areas you code. Tip 20 Use Tracer Bullets to Find the Target

Chapter 2, A Pragmatic Approach, página 111

Once you’re on target, adding functionality is easy.

Tracer code is not disposable: you write it for keeps. It contains all the error checking, structuring, documentation, and self-checking that any piece of production code has. It simply is not fully functional. However, once you have achieved an end-to-end connection among the components of your system, you can check how close to the target you are, adjusting if necessary. Once you’re on target, adding functionality is easy.

Chapter 2, A Pragmatic Approach, página 113

Prototype to Learn

Prototyping is a learning experience. Its value lies not in the code produced, but in the lessons learned. That’s really the point of prototyping. Tip 21 Prototype to Learn

Chapter 2, A Pragmatic Approach, página 120

Program Close to the Problem Domain

Computer languages influence how you think about a problem, and how you think about communicating. Every language comes with a list of features: buzzwords such as static versus dynamic typing, early versus late binding, functional versus OO, inheritance models, mixins, macros — all of which may suggest or obscure certain solutions. Designing a solution with C++ in mind will produce different results than a solution based on Haskell-style thinking, and vice versa. Conversely, and we think more importantly, the language of the problem domain may also suggest a programming solution. We always try to write code using the vocabulary of the application domain (see Maintain a Glossary). In some cases, Pragmatic Programmers can go to the next level and actually program using the vocabulary, syntax, and semantics — the language — of the domain. Tip 22 Program Close to the Problem Domain

Chapter 2, A Pragmatic Approach, página 124

Estimate to Avoid Surprises

By learning to estimate, and by developing this skill to the point where you have an intuitive feel for the magnitudes of things, you will be able to show an apparent magical ability to determine their feasibility. When someone says “we’ll send the backup over a network connection to S3,” you’ll be able to know intuitively whether this is practical. When you’re coding, you’ll be able to know which subsystems need optimizing and which ones can be left alone. Tip 23 Estimate to Avoid Surprises

Chapter 2, A Pragmatic Approach, página 133

The trick is to work out which parameters have the most impact on the result, and concentrate on getting them about right.

Typically, parameters whose values are added into a result are less significant than those that are multiplied or divided. Doubling a line speed may double the amount of data received in an hour, while adding a 5ms transit delay will have no noticeable effect.

Chapter 2, A Pragmatic Approach, página 137

Iterate the Schedule with the Code

That’s also how the old joke says to eat an elephant: one bite at a time. Tip 24 Iterate the Schedule with the Code

Chapter 2, A Pragmatic Approach, página 140

“This application will never be used abroad, so why internationalize it?” “count can’t be negative.” “Logging can’t fail.”

Let’s not practice this kind of self-deception, particularly when coding.

Chapter 4. Pragmatic Paranoia, página 207

Use Assertions to Prevent the Impossible

Whenever you find yourself thinking “but of course that could never happen,” add code to check it. The easiest way to do this is with assertions. In many language implementations, you’ll find some form of assert that checks a Boolean condition. These checks can be invaluable. If a parameter or a result should never be null, then check for it explicitly:

Chapter 4. Pragmatic Paranoia, página 207

Finish What You Start

This tip is easy to apply in most circumstances. It simply means that the function or object that allocates a resource should be responsible for deallocating it. Let’s see how it applies by looking at an example of some bad code — part of a Ruby program that opens a file, reads customer information from it, updates a field, and writes the result back. We’ve eliminated error handling to make the example clearer:

Chapter 4. Pragmatic Paranoia, página 212

Act Locally

In this topic we’re mostly looking at ephemeral resources used by your running process. But you might want to consider what other messes you might be leaving behind.

For instance, how are your logging files handled? You are creating data and using up storage space. Is there something in place to rotate the logs and clean them up? How about for your unofficial debug files you’re dropping? If you’re adding logging records in a database, is there a similar process in place to expire them? For anything that you create that takes up a finite resource, consider how to balance it.

What else are you leaving behind?

Chapter 4. Pragmatic Paranoia, página 216

Don’t Outrun Your Headlights

It’s late at night, dark, pouring rain. The two-seater whips around the tight curves of the twisty little mountain roads, barely holding the corners. A hairpin comes up and the car misses it, crashing though the skimpy guardrail and soaring to a fiery crash in the valley below. State troopers arrive on the scene, and the senior officer sadly shakes their head. “Must have outrun their headlights.”

Had the speeding two-seater been going faster than the speed of light? No, that speed limit is firmly fixed. What the officer referred to was the driver’s ability to stop or steer in time in response to the headlight’s illumination.

Headlights have a certain limited range, known as the throw distance. Past that point, the light spread is too diffuse to be effective. In addition, headlights only project in a straight line, and won’t illuminate anything off-axis, such as curves, hills, or dips in the road. According to the National Highway Traffic Safety Administration, the average distance illuminated by low-beam headlights is about 160 feet. Unfortunately, stopping distance at 40mph is 189 feet, and at 70mph a whopping 464 feet. So indeed, it’s actually pretty easy to outrun your headlights.

Chapter 4. Pragmatic Paranoia, página 222

Take Small Steps — Always

Always take small, deliberate steps, checking for feedback and adjusting before proceeding. Consider that the rate of feedback is your speed limit. You never take on a step or a task that’s “too big.”

Chapter 4. Pragmatic Paranoia, página 223

Instead of wasting effort designing for an uncertain future, you can always fall back on designing your code to be replaceable.

Make it easy to throw out your code and replace it with something better suited. Making code replaceable will also help with cohesion, coupling, and DRY, leading to a better design overall.

Chapter 4. Pragmatic Paranoia, página 224

Avoid Fortune-Telling

Much of the time, tomorrow looks a lot like today. But don’t count on it.

Chapter 4. Pragmatic Paranoia, página 225

Coupling is the enemy of change, because it links together things that must change in parallel.

This makes change more difficult: either you spend time tracking down all the parts that need changing, or you spend time wondering why things broke when you changed “just one thing” and not the other things to which it was coupled.

Chapter 5. Bend, or Break, página 228

Decoupled Code Is Easier to Change

Given that we don’t normally code using steel beams and rivets, just what does it mean to decouple code? Avoid this three topics:

  • Train wrecks — chains of method calls
  • Globalization — the dangers of static things
  • Inheritance — why subclassing is dangerous

Chapter 5. Bend, or Break, página 229

Tell, Don’t Ask

This principle says that you shouldn’t make decisions based on the internal state of an object and then update that object. Doing so totally destroys the benefits of encapsulation and, in doing so, spreads the knowledge of the implementation throughout the code. So the first fix for our train wreck is to delegate the discounting to the total object:

Chapter 5. Bend, or Break, página 231

If It’s Important Enough to Be Global, Wrap It in an API

Any mutable external resource is global data. If your application uses a database, datastore, file system, service API, and so on, it risks falling into the globalization trap. Again, the solution is to make sure you always wrap these resources behind code that you control.

Chapter 5. Bend, or Break, página 237

Parameterize Your App Using External Configuration

When code relies on values that may change after the application has gone live, keep those values external to the app. When your application will run in different environments, and potentially for different customers, keep the environment- and customer-specific values outside the app. In this way, you’re parameterizing your application; the code adapts to the places it runs.

Chapter 5. Bend, or Break, página 284

Shared State Is Incorrect State

The problem is the shared state. Each server in the restaurant looked into the display case without regard for the other. Each point-of-sale device looked at an account balance without regard for the other.

Chapter 6. Concurrency, página 299

--

--

Alfredo Bautista Santos
Alfredo Bautista Santos

Written by Alfredo Bautista Santos

Sysadmin and web developer. Co-organizer of GDGMarbella & FlutterConf in Marbella, Spain. Flutter enthusiastic.

No responses yet