The Pragmatic Programmer

The Pragmatic Programmer

From Journeyman to Master

By David Thomas and Andrew Hunt

Summary

Like any other craft, computer programming has spawned a body of wisdom, most of which isn't taught at universities or in certification classes. Most programmers arrive at the so-called tricks of the trade over time, through independent experimentation.

In The Pragmatic Programmer, Andrew Hunt and David Thomas codify many of the truths they've discovered during their respective careers as designers of software and writers of code.

Some of the authors' nuggets of pragmatism are concrete, and the path to their implementation is clear. They advise readers to learn one text editor, for example, and use it for everything. They also recommend the use of version-tracking software for even the smallest projects, and promote the merits of learning regular expression syntax and a text-manipulation language. Other (perhaps more valuable) advice is more light-hearted. In the debugging section, it is noted that, "if you see hoof prints think horses, not zebras." That is, suspect everything, but start looking for problems in the most obvious places. There are recommendations for making estimates of time and expense, and for integrating testing into the development process.

You'll want a copy of The Pragmatic Programmer for two reasons: it displays your own accumulated wisdom more cleanly than you ever bothered to state it, and it introduces you to methods of work that you may not yet have considered.

Quotes

Preface to the second edition

But 20 years is many lifetimes in terms of software. Take a developer from 1999 and drop them into a team today, and they’d struggle in this strange new world. But the world of the 1990s is equally foreign to today’s developer. The book’s references to things such as CORBA, CASE tools, and indexed loops were at best quaint and more likely confusing. At the same time, 20 years has had no impact whatsoever on common sense. Technology may have changed, but people haven’t. Practices and approaches that were a good idea then remain a good idea now. Those aspects of the book aged well.

"20 yearS had no impact on common sense" made me laugh

Preface to the first edition

Great lawns need small amounts of daily care, and so do great programmers. Management consultants like to drop the word kaizen in conversations. “Kaizen” is a Japanese term that captures the concept of continuously making many small improvements.

Gains come from compounding consistency

1. A pragmatic philosophy

It is your life. You own it. You run it. You create it. Many developers we talk to are frustrated. Their concerns are varied. Some feel they’re stagnating in their job, others that technology has passed them by. Folks feel they are under appreciated, or underpaid, or that their teams are toxic. Maybe they want to move to Asia, or Europe, or work from home.

Made me think of my own efforts to work remotely/etc.

Responsibility is something you actively agree to. You make a commitment to ensure that something is done right, but you don’t necessarily have direct control over every aspect of it. In addition to doing your own personal best, you must analyze the situation for risks that are beyond your control. You have the right not to take on a responsibility for an impossible situation, or one in which the risks are too great, or the ethical implications too sketchy. You’ll have to make the call based on your own values and judgment.

Today i dont fully account for risks. At drppbox in particular team dependencies are huge risks.

There are many factors that can contribute to software rot. The most important one seems to be the psychology, or culture, at work on a project.

Tech debt is generally psychogical or cultural

Don’t leave “broken windows’’ (bad designs, wrong decisions, or poor code) un-repaired. Fix each one as soon as it is discovered. If there is insufficient time to fix it properly, then board it up. Perhaps you can comment out the offending code, or display a “Not Implemented” message, or substitute dummy data instead. Take some action to prevent further damage and to show that you’re on top of the situation.

This broken window theory is relevant to that forgotten password bug i resolved.

We like to think of all the facts programmers know about computing, the application domains they work in, and all their experience as their knowledge portfolios. Managing a knowledge portfolio is very similar to managing a financial portfolio: Serious investors invest regularly—as a habit. Diversification is the key to long-term success. Smart investors balance their portfolios between conservative and high-risk, high-reward investments. Investors try to buy low and sell high for maximum return. Portfolios should be reviewed and rebalanced periodically. To be successful in your career, you must invest in your knowledge portfolio using these same guidelines.

validation for how ive been thinking about this. I need to read more books.

2. A pragmatic approach

A thing is well designed if it adapts to the people who use it. For code, that means it must adapt by changing. So we believe in the ETC principle: Easier to Change. ETC. That’s it. As far as we can tell, every design principle out there is a special case of ETC. Why is decoupling good? Because by isolating concerns we make each easier to change. ETC. Why is the single responsibility principle useful? Because a change in requirements is mirrored by a change in just one module. ETC. Why is naming important? Because good names make code easier to read, and you have to read it to change it. ETC!

This is a guide, not a rule. Easier to change is better

Not All Code Duplication Is Knowledge Duplication - As part of your online wine ordering application you’re capturing and validating your user’s age, along with the quantity they’re ordering. According to the site owner, they should both be numbers, and both greater than zero. So you code up the validations:   def validate_age(value): validate_type(value, :integer) validate_min_integer(value, 0) def validate_quantity(value): validate_type(value, :integer) validate_min_integer(value, 0) During code review, the resident know-it-all bounces this code, claiming it’s a DRY violation: both function bodies are the same. They are wrong. 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.

Good example of when not to DRY. Think of the knowledge represented and it might be cpoincidence they r same

Suppose you have a cat in a closed box, along with a radioactive particle. The particle has exactly a 50% chance of fissioning into two. If it does, the cat will be killed. If it doesn’t, the cat will be okay. So, is the cat dead or alive? According to Schrödinger, the correct answer is both (at least while the box remains closed). Every time a subnuclear reaction takes place that has two possible outcomes, the universe is cloned. In one, the event occurred, in the other it didn’t. The cat’s alive in one universe, dead in another. Only when you open the box do you know which universe you are in.

Makes me think whether life actually revolves around ourselves. Eg if i didnt exist then i wouldnt be observing this universe? Therefore i must only be observing a universe I'm alive in? Its tautological

Tracer bullets are loaded at intervals alongside regular ammunition. When they’re fired, their phosphorus ignites and leaves a pyrotechnic trail from the gun to whatever they hit. If the tracers are hitting the target, then so are the regular bullets. Soldiers use these tracer rounds to refine their aim: it’s pragmatic, real-time feedback under actual conditions.

Applying this concept to software, goal is to build projects that give you end to end feedback iteratively. Go vertical at first, then optimize each layer horizontally.

make sure app compiles and says hello world is a good start

3. The basic tools

4. Pragmatic paranoia

Catch and Release Is for Fish Some developers feel that is it good style to catch or rescue all exceptions, re-raising them after writing some kind of message.

This implicitly couples functions with callers. If new exception thrown it is handled differently

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.

Just as the car headlights have limited throw, we can only see into the future perhaps one or two steps, maybe a few hours or days at most. Beyond that, you can quickly get past educated guess and into wild speculation. You might find yourself slipping into fortune telling when you have to: Estimate completion dates months in the future Plan a design for future maintenance or extendability Guess user’s future needs Guess future tech availability But, we hear you cry, aren’t we supposed to design for future maintenance? Yes, but only to a point: only as far ahead as you can see. The more you have to predict what the future will look like, the more risk you incur that you’ll be wrong.

Get feedback early and often, prepare to not know the unknown

5. Bend, or break

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.

Anything that is an implementation detail should be masked. Instead of customer.orders.find(), expose a findOrder() function.

Don’t Chain Method Calls Try not to have more than one “.” when you access something. And access something also covers cases where you use intermediate variables, as in the following code:   #
# This is pretty poor style  
amount = customer.orders.last().totals().amount;
# and so is this…
orders = customer.orders;
last = orders.last();
totals = last.totals();
amount = totals.amount;

Good advice, consistent with Clean Coder

This is a very powerful abstraction: we no longer need to think about time as being something we have to manage. Event streams unify synchronous and asynchronous processing behind a common, convenient API.
import * as Observable from 'rxjs'
import { mergeMap }    from 'rxjs/operators' 
import { ajax }        from 'rxjs/ajax'
import { logValues }   from "../rxcommon/logger.js"
let users = Observable.of(3, 2, 1)
let result = users.pipe(   
  mergeMap((user) => ajax.getJSON(`https://reqres.in/api/users/${user}`))   
)
result.subscribe(
  resp => logValues(JSON.stringify(resp.data)),   err  => console.error(JSON.stringify(err))   )

Interesting takeaway on observables. Same code works for async o sync.

You might think that this is just syntactic sugar. But in a very real way the pipeline operator is a revolutionary opportunity to think differently. Using a pipeline means that you’re automatically thinking in terms of transforming data; each time you see |> you’re actually seeing a place where data is flowing between one transformation and the next.

Interesting perspective on benefit of pipe operator

Use Mixins to Share Functionality Inheritance Is Rarely the Answer We’ve had a quick look at three alternatives to traditional class inheritance: Interfaces and protocols Delegation Mixins and traits Each of these methods may be better for you in different circumstances, depending on whether your goal is sharing type information, adding functionality, or sharing methods. As with anything in programming, aim to use the technique that best expresses your intent.

Inheritance increases coupling. Opt for has-a vs is-A. Alternatively use  interfaces or mixins

6. Concurrency

In the actor model, there’s no need to write any code to handle concurrency, as there is no shared state. There’s also no need to code in explicit end-to-end “do this, do that” logic, as the actors work it out for themselves based on the messages they receive.

Actor model kind of like an event system used for async processing where the callbacks can interact w each other or re passed around

7. While you are coding

Only human beings can look directly at something, have all the information they need to make an accurate prediction, perhaps even momentarily make the accurate prediction, and then say that it isn’t so.

This quote is in context of listening to instincts. Reminds me of my perception of dropbox before joining was spot on.

You refactor when you’ve learned something; when you understand something better than you did last year, yesterday, or even just ten minutes ago. Perhaps you’ve come across a stumbling block because the code doesn’t quite fit anymore, or you notice two things that should really be merged, or anything else at all strikes you as being “wrong,” don’t hesitate to change it.

If you have new information not present when fn was created, refactor

Build End-to-End, Not Top-Down or Bottom Up We strongly believe that the only way to build software is incrementally. Build small pieces of end-to-end functionality, learning about the problem as you go. Apply this learning as you continue to flesh out the code, involve the customer at each step, and have them guide the process.

Bottoms up doesn’t know right abstractions, top doesn’t have requirements for big picture. Think of product development as filling glass with balls, sand, pebbles. Do big end to end first then fill out rather than adding too much detail

We have an instance method that discounts an order  public void deductPercent ( double amount ) First , deductPercent is what it does and not why it does it . Then the name of the parameter amount is at best misleading : is it an absolute amount , a percentage ? Perhaps this would be better : public void applyDiscount ( Percentage discount )

Interesting their take on naming things well is to name after why something is the way it is vs what it does

8. Before the project

In 1967 , Melvin Conway introduced an idea in How do Committees Invent ? [ Con68 ] which would become known as Conway’s Law : Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations .

This is where dont ship your org structure comes from i guess

We are uncovering better ways of developing software by doing it and helping others do it . Through this work we have come to value : Individuals and interactions over processes and tools Working software over comprehensive documentation Customer collaboration over contract negotiation Responding to change over following a plan That is , while there is value in the items on the right , we value the items on the left more .

This feels counter to my experience at dbx

9. Pragmatic Projects

No Broken Windows Quality is a team issue . The most diligent developer placed on a team that just doesn’t care will find it difficult to maintain the enthusiasm needed to fix niggling problems . The problem is further exacerbated if the team actively discourages the developer from spending time on these fixes .

This would make for a good working agreement item

My thoughts

This book is basically a bunch of tips about how to be a more effective programmer. I sort of wish there was more to it than a series of things you should or should not do, as I find a list of commandments very hard to remember. Here's my favorite ones:

You have the agency to make change

One of the biggest takeaways in the book is that you have agency to make change. Invest in yourself and you will have compounding gains. This industry gives a remarkable set of opportunities. If you want to work remotely, ask and if they say no then find someone who says yes.

Account for risks and take responsibility

Another big takeaway was the points on estimation. You have a responsibility for anything you commit to. Make sure you identify risks on anything you are working on. You have the right to not take on a responsibility for an impossible situation or one in which the risks are too great.

Tech debt is psychological or cultural and software will tend to rot

The broken windows theory applies to software rot as well - if things are bad they’ll get worse. Also happens when things are messy in my house from what I’ve seen.

Good software design boils down to make things easier to change

E.g. readability is important because it makes things easier to change for other developers since they can understand the code easier. When we’re building software we should do it in a way that’s easiest to change. The Tracer Bullet concept is a great example of this, build end-to-end first and then horizontally. Don’t build too far ahead of yourself or you’ll encode assumptions that aren’t true.

Questions you should be able to answer after reading this book

How does the "broken windows" theory apply to software development?

In a neighborhood, just one broken window can cause the rest of the tenants or people to care less about the upkeep of the neighborhood, helping the whole neighborhood to fall into disrepair. A similar thing can happen in code (and really in many other places). A little bit of bad code justifies committing a little bit more bad code until the software is "rotten". Fixing small things as you go along is really important for not accumulating too much tech debt. Quality is a team issue . The most diligent developer placed on a team that just doesn’t care will find it difficult to maintain the enthusiasm needed to fix niggling problems . The problem is further exacerbated if the team actively discourages the developer from spending time on these fixes .

How do you design software for future maintenance?

Software should be built for maintenance, but with one key tenet, that you should not build too far ahead of what you can foresee. If you build an abstraction that does all the things that could possibly happen in the future, it's almost certainly going to be cumbersome to use. The more you have to predict what the future will look like, the more risk you incur that you’ll be wrong.

Ideally as you're building some software you're getting feedback quickly and never building too far ahead of what you know to be true about your user's needs or potential use cases.

What is "tracer bullet development"?

Tracer bullets are bullets loaded at intervals alongside regular ammunition. When they’re fired, their phosphorus ignites and leaves a pyrotechnic trail from the gun to whatever they hit. If the tracers are hitting the target, then so are the regular bullets. Soldiers use these tracer rounds to refine their aim: it’s pragmatic, real-time feedback under actual conditions. Tracer bullet development illustrates the need for immediate feedback under actual conditions with a moving goal. Your users may have never seen a system like this before, your may face large unknowns with algorithms, techniques, languages, or libraries you aren't familiar with. By getting basically an MVP of each component of the project working, you can start to get feedback about each part and improve from there.