My Key Takeaways from The Pragmatic Programmer: Part I
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