• No results found

Avoiding Duplicate Code

In document OO Programming in Python (Page 190-193)

Additional Control Structures

5.3 Avoiding Duplicate Code

The control structures we have introduced (loops, conditionals and functions) can be very valuable when programming. They provide convenient means for expressing tasks ele-gantly and succinctly. They help in designing code fragments that are reusable and easily maintained. Unfortunately, beginning programmers often miss opportunities to make best use of these constructs.

One common habit is to reuse a portion of code by simply copying it to another portion of a program, and modifying it if necessary. Text editors enable this style by supporting a paste operation. Yet there are relatively few cases where copy-and-paste is the best choice of techniques. As a general rule, if you are tempted to copy and paste lines of code, there is probably a better way to design your code through the use of control structures.

We will explore several examples in this section. To frame our discussion, we begin with a blatant misuse of copy-and-paste. A classic punishment in school is to write a sen-tence such as “I will not chew gum in class” one hundred times on the blackboard. This is a time-consuming task by hand, although clearly straightforward. In the context of com-puting, generating this sort of output is easily automated. Of course, one possible program for doing so would have 100 lines of code, starting as follows:

print'I will not chew gum in class' print'I will not chew gum in class' print'I will not chew gum in class' print'I will not chew gum in class' print'I will not chew gum in class' ...

Typing such a program seems burdensome, but with a basic text editor, the programmer could type the first line and then just repeatedly paste copies of that line to complete the rest of the program. While it’s obvious that a more elegant program might be written as for i in range(100):

print'I will not chew gum in class'

we want to discuss why the first version is such a bad program.

The biggest problem is that it is difficult to maintain. Suppose that we had inadver-tently misspelled a word when typing the original line, but did not notice this until after copying it. To fix that mistake, we would need to edit all 100 lines (or edit the first line and then repaste 99 copies of that fixed line). With the more elegant approach, we would only have to fix the mistake in one place. In similar fashion, the first program is not easily adaptable if we were instead asked to print some other phrase one hundred times (e.g., “I will not use my cell phone in class.”).

Now even a beginning programmer would probably recognize that such blatant rep-etition should be accomplished with a loop. But what about if the reprep-etitions are not identical? For example, we presented the following code on page 112 of Chapter 3 (albeit, before we had formally introduced control structures).

1 from time import sleep

2 timeDelay = .25 # one-quarter second

3 car.move(−10, 0) 4 sleep(timeDelay) 5 car.move(−30, 0) 6 sleep(timeDelay) 7 car.move(−60, 0) 8 sleep(timeDelay) 9 car.move(−100, 0) 10 sleep(timeDelay)

This could have been entered by typing the first four lines, then pasting three additional copies of lines 3 and 4 and modifying them to adjust the x-coordinate of the move. We call this a copy-paste-and-modify approach. If we wanted even more frames of such an animation, we could continue to paste and modify those lines.

Since the repetitions are not identical, the use of a loop may not seem obvious. But there is clearly a repetitive pattern. The key to using a control structure in such a case is to isolate the particular part of the pattern that is being changed, the “modify” that takes place after the paste. In this case, the only modification is the x-coordinate of the move.

So we should think of that as a variable when describing the repetition. For example, we could get the same effect with the following code:

from time import sleep timeDelay = .25

for deltaX in [10,30,60,100]:

car.move(deltaX, 0) sleep(timeDelay)

This code is significantly easier to maintain than the original. For example, we could easily divide all movements by two with a single change, usingdeltaX/2rather thandeltaX within the loop body. With the original code, we would have to edit each of the individual moves. We can add additional iterations to our new code by augmenting the list (it would be even more sophisticated to take advantage of the underlying pattern, namely that the car is accelerating to the left by ten units per iteration).

Functions

Designing useful functions is another classic way to avoid unnecessary duplication of code. Consider the structure of a typical song (for example as shown in Figure 5.5 from page 170). Often there is a verse, then a chorus, then a different verse, then a repeat of the same chorus, and so on. We want to avoid having duplicate code for each of the three chorus repetitions, but since the choruses are not repeated back-to-back, a loop may not be appropriate. Defining a single function that encapsulates the repeated task allows us to avoid the duplication. Once defined, the function can be called numerous times.

As a more tangible example of the wise use of functions, we revisit another example from the graphics chapter. The code for the animation case study from Section 3.9 has much unnecessary duplication. For example, constructing the target in lines 15–33 of Fig-ure 3.22 involves the creation of three different circles representing the rings. Each of those circles has a different radius, color, and depth. We could simplify that code by defining a separate function for generating a custom circle as follows:

def createRing(radius, color, depth):

c = Circle(radius) c.setFillColor(color) c.setDepth(depth) return c

With this function defined, lines 16–27 of the original code can be replaced as follows:

target.add(createRing(30,'white', 49)) target.add(createRing(20,'blue', 48)) target.add(createRing(10,'red', 47))

Conditional statements

Another common temptation involves the use of copy-and-paste when working with differ-ent branches of a conditional statemdiffer-ent. For example, consider the following code fragmdiffer-ent which might be used in software for the checkout process for a store’s website:

1 if destination =='Missouri': 2 subtotal = sum(shoppingCart) 3 tax = subtotal * 0.04225 4 total = subtotal + tax

5 elif destination =='Illinois': 6 subtotal = sum(shoppingCart) 7 tax = subtotal * 0.0625 8 total = subtotal + tax

We see that the program is calculating the total for a shopping cart, including sales tax based on the destination address. If the vendor operates in all 50 states, then presumably we might see versions of that four-line block repeated for each state.

Yet this program contains some unnecessary duplication, as the different branches are almost identical. The same technique is used to compute the total; the only difference between states is the tax-rate. Two of the three commands within each branch are not truly conditional. The commandsubtotal = sum(shoppingCart)is always executed first, no matter what state to which the order is being shipped. If that command is always executed, then it really should not be hidden within a conditional. We could move it ahead of the conditional, resulting in the following code.

subtotal = sum(shoppingCart) if destination =='Missouri':

tax = subtotal * 0.04225 total = subtotal + tax

elif destination =='Illinois': tax = subtotal * 0.0625

total = subtotal + tax

Similarly, the final command of each branch is the same, and so we could move it beyond the conditional, as follows:

subtotal = sum(shoppingCart) if destination =='Missouri':

tax = subtotal * 0.04225

elif destination =='Illinois': tax = subtotal * 0.0625

total = subtotal + tax

At this point, we have reduced the branching to the particular command that varies from state to state. There are only two lines of code per state in the new version versus four in the original. So with 50 states, 200 lines of code would be reduced to 100.

Still, the placement of the conditional within the flow of the program is somewhat distracting for a reader. We could further improve the readability of our program by defin-ing alookupTaxRatefunction for reporting the sales tax rate for a given state. We could then use the function as follows:

1 subtotal = sum(shoppingCart)

2 tax = subtotal * lookupTaxRate(destination) 3 total = subtotal + tax

The original branching is presumably displaced to within the function body. Yet this reor-ganization makes the primary portion of our code read more naturally.

In document OO Programming in Python (Page 190-193)