Page xxi
In order to be a Pragmatic Programmer, we’re challenging you to think about what you’re doing while you’re doing it. This isn’t a one-time audit of current practices—it’s an ongoing critical appraisal of every decision you make, every day, and on every project. Never run on auto-pilot. Constantly be thinking, critiquing your work in real time. The old IBM corporate motto, THINK!, is the Pragmatic Programmer’s mantra. If this sounds like hard work to you, then you’re exhibiting the realistic characteristic. This is going to take up some of your valuable time-time that is probably already under tremendous pressure. The reward is a more active involvement with a job you love, a feeling of mastery over an increasing range of subjects, and pleasure in a feeling of continuous improvement. Over the long term, your time investment will be repaid as you and your team become more efficient, write code that’s easier to maintain, and spend less time in meetings.
Page xxii
We who cut mere stones must always be envisioning cathedrals
Page 2
Does your work environment suck? Is your job boring? Try to fix it. But don’t try forever. As Martin Fowler says, “you can change your organization or change your organization.”
Page 18
Critical thinking
Ask the “Five Whys”
A favorite consulting trick: ask “why?” at least five times. Ask a question, and get an answer. Dig deeper by asking “why?” Repeat as if you were a petulant four-year old (but a polite one). You might be able to get closer to a root cause this way.
Who does this benefit?
It may sound cynical, but follow the money can be a very helpful path to analyze. The benefits to someone else or another organization may be aligned with your own, or not.
What’s the context?
Everything occurs in its own context, which is why “one size fits all” solutions often don’t. Consider an article or book touting a “best practice.” Good questions to consider are “best for who?” What are the prerequisites, what are the consequences, short and long term?
When or Where would this work?
Under what circumstances? Is it too late? Too early? Don’t stop with first-order thinking (what will happen next), but use second-order thinking: what will happen after that?
Why is this a problem?
Is there an underlying model? How does the underlying model work?
Unfortunately, there are very few simple answers anymore. But with your extensive portfolio, and by applying some critical analysis to the torrent of technical articles you will read, you can understand the complex answers.
Page 23
So restrict your non-API commenting to discussing why something is done, its purpose and its goal. The code already shows how it is done, so commenting on this is redundant—and is a violation of the DRY principle. Commenting source code gives you the perfect opportunity to document those elusive bits of a project that can’t be documented anywhere else: engineering trade-offs, why decisions were made, what other alternatives were discarded, and so on.
Page 29
On ETC - easier to change
ETC Is a Value, Not a Rule
Values are things that help you make decisions: should I do this, or that? When it comes to thinking about software, ETC is a guide, helping you choose between paths. Just like all your other values, it should be floating just behind your conscious thought, subtly nudging you in the right direction. But how do you make that happen? Our experience is that it requires some initial conscious reinforcement. You may need to spend a week or so deliberately asking yourself “did the thing I just did make the overall system easier or harder to change?” Do it when you save a file. Do it when you write a test. Do it when you fix a bug.
Page 43
There are several techniques you can use to maintain orthogonality: 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, on page 130. 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 that you’ll remain orthogonal. Avoid global data Every time your code references global data, it ties itself into the other components that share that data. Even globals that you intend only to read can lead to trouble (for example, if you suddenly need to change your code to be multithreaded). In general, your code is easier to understand and maintain if you explicitly pass any required context into your modules. In object-oriented applications, context is often passed as parameters to objects’ constructors. In other code, you can create structures containing the context and pass around references to them. The Singleton pattern in Design Patterns: Elements of Reusable Object-Oriented Software [GHJV95] is a way of ensuring that there is only one instance of an object of a particular class. Many people use these singleton objects as a kind of global variable (particularly in languages, such as Java, that otherwise do not support the concept of globals). Be careful with singletons-they can also lead to unnecessary linkage. Avoid similar functions 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 imple-mentation. Get into the habit of being constantly critical of your code. Look for any opportunities to reorganize it to improve its structure and orthogonality. This process is called refactoring, and it’s so important that we’ve dedicated a section to it (see Topic 40, Refactoring, on page 209).
Page 48
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.
Page 51
Code That Glows in the Dark Tracer bullets work because they operate in the same environment and under the same constraints as the real bullets. They get to the target fast, so the gunner gets immediate feedback. And from a practical standpoint they’re a relatively cheap solution. To get the same effect in code, we look for something that gets us from a requirement to some aspect of the final system quickly, visibly, and repeatably. 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.
Use Tracer Bullets to Find the Target
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.
Page 53
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. Tracer development is consistent with the idea that a project is never finished: there will always be changes required and functions to add. It is an incremental approach.
Page 69
Estimating Project Schedules Normally you’ll be asked to estimate how long something will take. If that “something” is complex, the estimate can be very difficult to produce. In this section, we’ll look at two techniques for reducing that uncertainty. Painting the Missile “How long will it take to paint the house?” “Well, if everything goes right, and this paint has the coverage they claim, it might be as few as 10 hours. But that’s unlikely: I’d guess a more realistic figure is closer to 18 hours. And, of course, if the weather turns bad, that could push it out to 30 or more.” That’s how people estimate in the real world. Not with a single number (unless you force them to give you one) but with a range of scenarios. When the U.S. Navy needed to plan the Polaris submarine project, they adopted this style of estimating with a methodology they called the Program Evaluation Review Technique, or PERT. Every PERT task has an optimistic, a most likely, and a pessimistic estimate. The tasks are arranged into a dependency network, and then you use some simple statistics to identify likely best and worst times for the overall project. Using a range of values like this is a great way to avoid one of the most common causes of estimation error: padding a number because you’re unsure. Instead, the statistics behind PERT spreads the uncertainty out for you, giving you better estimations of the whole project. However, we’re not big fans of this. People tend to produce wall-sized charts of all the tasks in a project, and implicitly believe that, just because they used a formula, they have an accurate estimate. The chances are they don’t, because they have never done this before.
Page 132
Train Wrecks We’ve all seen (and probably written) code like this:
public void applyDiscount(customer, order_id, discount) {
totals = customer
.orders
.find(order_id)
.getTotals);
totals. grandTotal = totals.grandTotal - discount;
totals.discount = discount;
}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:
public void applyDiscount(customer, order_id, discount) {
customer
.orders
.find(order_id)
.getTotals)
.applyDiscount(discount);
}We have the same kind of tell-don’t-ask (TDA) issue with the customer object and its orders: we shouldn’t fetch its list of orders and search them. We should instead get the order we want directly from the customer:
public void applyDiscount(customer, order_id, discount) (
findOrder(order_id)
.getTotals()
.applyDiscount(discount);
}The same thing applies to our order object and its totals. Why should the outside world have to know that the implementation of an order uses a separate object to store its totals?
public void applyDiscount(customer, order_id, discount) {
customer
.findorder(order_id)
.applyDiscount(discount);
}And this is where we’d probably stop.
At this point you might be thinking that TDA would make us add an applyDis-countToOrder(order_id) method to customers. And, if followed slavishly, it would.
But TDA is not a law of nature; it’s just a pattern to help us recognize problems. In this case, we’re comfortable exposing the fact that a customer has orders, and that we can find one of those orders by asking the customer object for it. This is a pragmatic decision.
In every application there are certain top-level concepts that are universal. In this application, those concepts include customers and orders. It makes no sense to hide orders totally inside customer objects: they have an existence of their own. So we have no problem creating APIs that expose order objects.
Page 134
The Law of Demeter
The LoD says that a method defined in a class C should only call:
- Other instance methods in C
- Its parameters
- Methods in objects that it creates, both on the stack and in the heap
- Global variables
In the first edition of this book we spent some time describing the LoD. In the intervening 20 years the bloom has faded on that particular rose. We now don’t like the “global variable” clause (for reasons well go into in the next section). We also discovered that it’s difficult to use this in practice: it’s a little like having to parse a legal document whenever you call a method.
However, the principle is still sound. We just recommend a somewhat simpler way of expressing almost the same thing:
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;There’s a big exception to the one-dot rule: the rule doesn’t apply if the things you’re chaining are really, really unlikely to change. In practice, anything in your application should be considered likely to change. Anything in a third-party library should be considered volatile, particularly if the maintainers of that library are known to change APIs between releases. Libraries that come with the language, however, are probably pretty stable, and so we’d be happy with code such as:
people
.sort_by {person person.age }
.first(10)
.map {| person | person.name }Page 164
Mixins are a way of creating objects with common behaviour without the burden of coupling that inheritance brings.
Mixins, Traits, Categories, Protocol Extensions, …
As an industry, we love to give things names. Guite often we’ll give the same thing many names. More is better, right?
That’s what were dealing with when we look at mixins. The basic idea is simple: we want to be able to extend classes and objects with new functionality without using inheritance. So we create a set of these functions, give that set a name, and then somehow extend a class or object with them. At that point, you’ve created a new class or object that combines the capabilities of the original and all its mixins. In most cases, you’ll be able to make this extension even if you don’t have access to the source code of the class you’re extending.
Now the implementation and name of this feature varies between languages.
We’ll tend to call them mixins here, but we really want you to think of this as a language-agnostic feature. The important thing is the capability that all these implementations have: merging functionality between existing things and new things.
As an example, let’s go back to our AccountRecord example. As we left it, an AccountRecord needed to know about both accounts and about our persistence framework. It also needed to delegate all the methods in the persistence layer that it wanted to expose to the outside world.
Mixins give us an alternative. First, we could write a mixin that implements (for example) two of three of the standard finder methods. We could then add them into AccountRecord as a mixin. And, as we write new classes for persisted things, we can add the mixin to them, too:
mixin CommonFinders {
def find(id) { ... }
def findall() { ... }
end
class AccountRecord extends BasicRecord with CommonFinders
class OrderRecord extends BasicRecord with CommonFindersPage 169
Concurrency is when the execution of two or more pieces of code act as if they run at the same time. Parallelism is when they do run at the same time.
Page 224
Contracts, Invariants, and Properties In Topic 23, Design by Contract, on page 104, we talked about the idea that code has contracts that it meets: you meet the conditions when you feed it input, and it will make certain guarantees about the outputs it produces. There are also code invariants, things that remain true about some piece of state when it’s passed through a function. For example, if you sort a list, the invariant. result will have the same number of elements as the original-the length is Once we work out our contracts and invariants (which we’re going to lump together and call properties) we can use them to automate our testing. What we end up doing is called property based testing.
Property-based testing I’ve seen referred to as data-driven testing.
Page 277
Testing the Tests Because we can’t write perfect software, it follows that we can’t write perfect test software either. We need to test the tests. Think of our set of test suites as an elaborate security system, designed to sound the alarm when a bug shows up. How better to test a security system than to try to break in? After you have written a test to detect a particular bug, cause the bug deliberately and make sure the test complains. This ensures that the test will catch the bug if it happens for real.