Program Intently and Expressively

Một phần của tài liệu Pragprog practices of an agile developer (Trang 110 - 115)

“Code that works and is understandable is nice, but it’s more important to be clever. You’re paid for being smart; show us how good you are.”

Hoare on Software Design by C.A.R. Hoare

There are two ways of creating a software design. One way is to make it so simple that there are obviously no deficiencies. And the other way is to make it so complicated that there are no obvious deficiencies.

You’ve probably seen a lot of code that’s hard to understand, that’s hard to maintain, and (worst of all) has errors. You can tell code is bad when developers circle around it like spectators near a UFO—with the same mix of apprehension, confusion, and helplessness. What good is a piece of code if no one can understand how it works?

When developing code, you should always choose readability over con- venience. Code will be read many, many more times than it is written;

it’s well worth it to take a small performance hit during writing if it makes the reading easier. In fact, code clarity comes before execution performances as well.

For instance, if default or optional arguments are going to make your code less readable, less understandable, or buggier, it would be better to specify the arguments explicitly, rather than cause later confusion.

When you modify a piece of code to fix a bug or add new feature, try to approach it systematically. First, you have to understand what the code does and how it works. Then, you need to figure out what you’re going to change. You then make your changes and test. The first of these steps, understanding the code, is often the hardest. If someone hands you code that’s easy to understand, they’re making your life a lot easier. Honoring the Golden Rule, you owe it to them to make your own code easy to read.

One way to make code understandable is to make it obvious to see what’s happening. Let’s look at some examples:

coffeeShop.PlaceOrder(2);

PROGRAMINTENTLY ANDEXPRESSIVELY 101

Reading the above code, you can probably figure out that we’re placing an order at a coffee shop. But, what in the world is 2? Does that mean two cups of coffee? Two shots? Or is it the size of the cup? The only way for you to be certain is to look at the method definition or the documentation. This code isn’t easy to understand by reading.

So we add some comments to make the code easier to understand:

coffeeShop.PlaceOrder(2 /* large cup */);

That’s a tad better, but this is an occasion where commenting is used to compensate for poor code (Practice 26, Communicate in Code, on page105).

Java 5 and .NET (among others) have the concept of enumerated val- ues. Let’s use it. We can define an enum namedCoffeeCupSizein C# as the following:

public enum CoffeeCupSize {

Small, Medium, Large }

Then we can use it to order coffee:

coffeeShop.PlaceOrder(CoffeeCupSize.Large);

This code makes it obvious that we are placing an order for a large1cup of coffee.

As a developer, you should always be asking yourself whether there are ways to make your code easier to understand. Here’s another one:

Line 1 public int compute(int val)

- {

- int result = val << 1;

- //... more code ...

5 return result;

- }

What’s up with the shift operator in line 3? If you’re an experienced bit twiddler or familiar with logic design or assembly programming, then you may have figured that we just multiplied the value invalby 2.

1That’sventito you Starbucks fans.

Report erratum

PROGRAMINTENTLY ANDEXPRESSIVELY 102

The PIE Principle

Code you write must clearly communicate your intent and must be expressive. By doing so, your code will be readable and understandable. Since your code is not confusing, you will also avoid some potential errors. Program Intently and Expressively.

But what about folks who may not have that background—will they figure that out? Perhaps you have some inexperienced team members who only recently made a career change into programming. These folks will scratch their heads until their hair falls out.2 Although the code may be efficient, it lacks intent and expressiveness.

Shifting to multiply is an example of unnecessary and dangerous per- formance optimization. result = val*2 is clearer, works, and is proba- bly even more efficient given a decent compiler (old habits die hard;

see Practice 7, Know When to Unlearn, on page 34). Instead of being too clever and opaque, follow the PIE principle: Program Intently and Expressively (see the sidebar on this page).

Violating the PIE principle can go beyond readability or understand- ability of code—it can affect its correctness. Here’s a C# method that tries to synchronize calls to theMakeCoffee( ) method of aCoffeeMaker:

public void MakeCoffee() {

lock(this) {

// ... operation }

}

The author of this method wanted to define a critical section—at most one thread may execute the code in operation at any instant. To do that, the writer claimed a lock on the CoffeeMaker instance. A thread may execute this method only if it can acquire that lock. (In Java, you would usesynchronizedinstead oflock, but the idea is the same.) Although the code may look reasonable to any Java or .NET program- mer, it has two subtle problems. First, the lock is too sweeping, and

2Yeah, that’s not a bald spot; it’s a solar panel for a coding machine....

PROGRAMINTENTLY ANDEXPRESSIVELY 103

second, you are claiming a lock on a globally visible object. Let’s look at both these issues further.

Assume the coffeemaker can also dispense hot water, for those fans of a little Earl Gray in the morning. Suppose I want to synchronize the GetWater( ) method, so I call lock(this)within it. This synchronizes any code that useslockon theCofeeMakerinstance. That means you cannot make coffee and get hot water at the same time. Is that my intent, or did the lock become too sweeping? It’s not clear from reading the code, and you, the user of this code, are left wondering.

Also, the MakeCoffee( ) method implementation claims a lock on the CoffeeMakerobject, which is visible to the rest of the application. What if instead you lock theCoffeeMakerinstance in one thread and then call theMakeCoffee( ) method on that instance from another thread? At best it may lead to poor performance, and at worst it may lead to deadlock.

Let’s apply the PIE principle to this code, modifying it to make it more explicit. You want to keep more than one thread from executing the MakeCoffee( ) method at the same time. So, why not create an object specifically for that purpose and lock it?

private object makeCoffeeLock = new object();

public void MakeCoffee() {

lock(makeCoffeeLock) {

// ... operation }

}

This code addresses both the concerns we discussed—we rely on an explicit object to synchronize, and we express our intent more clearly.

When writing code, use language features to be expressive. Use method names that convey the intent; name method parameters to help readers understand their purpose. Exceptions convey what could go wrong and how to program defensively; use them and name them appropriately.

Good coding discipline can help make the code more understandable while reducing the need for unnecessary comments and other docu- mentation.

Report erratum

PROGRAMINTENTLY ANDEXPRESSIVELY 104

Write code to be clear, not clever. Express your inten- tions clearly to the reader of the code. Unreadable code isn’t clever.

What It Feels Like

You feel you—or anyone else on the team—can understand a piece of code you wrote a year ago and know exactly what it does in just one reading.

Keeping Your Balance

• What’s obvious to you now may not be obvious to others, or to you in a year’s time. Consider your coding to be a kind of time capsule that will be opened in some unknowing future.

• There is no later. If you can’t do it right now, you won’t be able to do it right later.

• Writing with intent doesn’t mean creating more classes or types.

It’s not an excuse for overabstraction.

• Use coupling that matches the situation: for instance, loose cou- pling via a hash table is intended for a situation where the compo- nents really are loosely coupled in real life. Don’t use it for compo- nents that are tightly coupled, because that doesn’t express your intent clearly.

COMMUNICATE INCODE 105

Một phần của tài liệu Pragprog practices of an agile developer (Trang 110 - 115)

Tải bản đầy đủ (PDF)

(199 trang)