The Copy Program


The Copy Program

A Familiar Scenario

Watching a design rot may help illustrate the preceding points. Let's say that your boss comes to you early Monday morning and asks you to write a program that copies characters from the keyboard to the printer. Doing some quick mental exercises in your head, you conclude that this will be less than ten lines of code. Design and coding time should be a lot less than 1 hour. What with cross-functional group meetings, quality education meetings, daily group progress meetings, and the three current crises in the field, this program ought to take you about a week to completeif you stay after hours. However, you always multiply your estimates by 3.

"Three weeks," you tell your boss. He harumphs and walks away, leaving you to your task.

The initial design

You have a bit of time right now before that process review meeting begins, so you decide to map out a design for the program. Using structured design, you come up with the structure chart in Figure 7-1.

Figure 7-1. Copy program structure chart


There are three modules, or subprograms, in the application. The Copy module calls the other two. The Copy program fetches characters from the Read Keyboard module and routes them to the Write Printer module.

You look at your design and see that it is good. You smile and then leave your office to go to that review. At least you'll be able to get a little sleep there.

On Tuesday, you come in a bit early so that you can finish up the Copy program. Unfortunately, one of the crises in the field has warmed up overnight, and you have to go to the lab and help debug a problem. On your lunch break, which you finally take at 3 PM, you manage to type in the code for the Copy program. The result is Listing 7-1.

You just manage to save the edit when you realize that you are already late for a quality meeting. You know that this is an important one; they are going to be talking about the magnitude of zero defects. So you wolf down your Twinkies and Coke and head off to the meeting.

Listing 7-1. The Copy Program

public class Copier {   public static void Copy()   {     int c;     while((c=Keyboard.Read()) != -1)       Printer.Write(c);   } }

On Wednesday, you come in early again, and this time nothing seems to be amiss. So you pull up the source code for the Copy program and begin to compile it. Lo and behold, it compiles first time with no errors! It's a good thing, too, because your boss calls you into an unscheduled meeting about the need to conserve laser printer toner.

On Thursday, after spending 4 hours on the phone walking a service technician in Rocky Mount, North Carolina, through the remote debugging and error-logging commands in one of the more obscure components of the system, you grab a Hoho and then test your Copy program. It works, first time! Good thing, too. Because your new co-op student has just erased the master source code directory from the server, and you have to go find the latest backup tapes and restore it. Of course, the last full backup was taken 3 months ago, and you have 94 incremental backups to restore on top of it.

Friday is completely unbooked. Good thing, too, because it takes all day to get the Copy program successfully loaded into your source code control system.

Of course, the program is a raging success and gets deployed throughout your company. Your reputation as an ace programmer is once again confirmed, and you bask in the glory of your achievements. With luck, you might actually produce 30 lines of code this year!

The requirements they are a'changin'

A few months later, your boss comes to you and says that the Copy program should also be able to read from the paper tape reader. You gnash your teeth and roll your eyes. You wonder why people are always changing the requirements. Your program wasn't designed for a paper tape reader! You warn your boss that changes like this are going to destroy the elegance of your design. Nevertheless, your boss is adamant, saying that the users really need to read characters from the paper tape reader from time to time.

So you sigh and plan your modifications. You'd like to add a Boolean argument to the Copy function. If TRue, you'd read from the paper tape reader; if false, you'd read from the keyboard as before. Unfortunately, so many other programs use the Copy program now that you can't change the interface. Changing the interface would cause weeks and weeks of recompiling and retesting. The system test engineers alone would lynch you, not to mention the seven people in the configuration control group. And the process police would have a field day, forcing all kinds of code reviews for every module that called Copy!

No, changing the interface is out. But then how can you let the Copy program know that it must read from the paper tape reader? Of course! You'll use a global! You'll also use the best and most useful feature of the C family of languages, the ?: operator! Listing 7-2 shows the result.

Listing 7-2. First modification of Copy program

public class Copier {   //remember to reset this flag   public static bool ptFlag = false;   public static void Copy()   {     int c;     while((c=(ptFlag ? PaperTape.Read()                       : Keyboard.Read())) != -1)       Printer.Write(c);   } }

Copy callers who want to read from the paper tape reader must first set the ptFlag to true. Then they can call Copy, and it will happily read from the paper tape reader. Once Copy returns, the caller must reset the ptFlag; otherwise, the next caller may mistakenly read from the paper tape reader rather than from the keyboard. To remind the programmers of their duty to reset this flag, you have added an appropriate comment.

Once again, you release your software to critical acclaim. It is even more successful than before, and hordes of eager programmers are waiting for an opportunity to use it. Life is good.

Give 'em an inch

Some weeks later, your bosswho is still your boss despite three corporatewide reorganizations in as many monthstells you that the customers would sometimes like the Copy program to output to the paper tape punch. Customers! They are always ruining your designs. Writing software would be a lot easier if it weren't for customers. You tell your boss that these incessant changes are having a profound negative effect on the elegance of your design, warning that if changes continue at this horrid pace, the software will be impossible to maintain before year's end. Your boss nods knowingly and then tells you to make the change anyway.

This design change is similar to the one before it. All we need is another global and another ?: operator! Listing 7-3 shows the result of your endeavors.

You are especially proud of the fact that you remembered to change the comment. Still, you worry that the structure of your program is beginning to topple. Any more changes to the input device will certainly force you to completely restructure the while loop conditional. Perhaps it's time to dust off your resume.

Listing 7-3. Second modification of Copy program

public class Copier {   //remember to reset these flags   public static bool ptFlag = false;   public static bool punchFlag = false;   public static void Copy()   {     int c;     while((c=(ptFlag ? PaperTape.Read()                       : Keyboard.Read())) != -1)       punchFlag ? PaperTape.Punch(c) : Printer.Write(c);   } }

Expect changes

I'll leave it to you to determine just how much of the preceding was satirical exaggeration. The point of the story is to show how the design of a program can rapidly degrade in the presence of change. The original design of the Copy program was simple and elegant. Yet after only two changes, it has begun to show the signs of rigidity, fragility, immobility, complexity, redundancy, and opacity. This trend is certainly going to continue, and the program will become a mess.

We might sit back and blame this on the changes. We might complain that the program was well designed for the original spec and that the subsequent changes to the spec caused the design to degrade. However, this ignores one of the most prominent facts in software development: Requirements always change!

Remember, the most volatile things in most software projects are the requirements. The requirements are continuously in a state of flux. This is a fact that we, as developers, must accept! We live in a world of changing requirements, and our job is to make sure that our software can survive those changes. If the design of our software degrades because the requirements have changed, we are not being agile.

Agile Design of the Copy Program

An agile development team might begin exactly the same way, with the code in Listing 7-1.[3] When the boss asked to make the program read from the paper tape reader, the developers would have responded by changing the design to be resilient to that kind of change. The result might have been something like Listing 7-4.

[3] Actually, the practice of test-driven development would very likely force the design to be flexible enough to endure the boss without change. However, in this example, we'll ignore that.

Listing 7-4. Agile version 2 of Copy

public interface Reader {   int Read(); } public class KeyboardReader : Reader {   public int Read() {return Keyboard.Read();} } public class Copier {   public static Reader reader = new KeyboardReader();   public static void Copy()   {     int c;     while((c=(reader.Read())) != -1)       Printer.Write(c);   } }

Instead of trying to patch the design to make the new requirement work, the team seizes the opportunity to improve the design so that it will be resilient to that kind of change in the future. From now on, whenever the boss asks for a new kind of input device, the team will be able to respond in a way that does not cause degradation to the Copy program.

The team has followed the Open/Closed Principle (OCP), which we describe in Chapter 9. This principle directs us to design our modules so that they can be extended without modification. That's exactly what the team has done. Every new input device that the boss asks for can be provided without modifying the Copy program.

Note, however, that when it first designed the module, the team did not try to anticipate how the program was going to change. Instead, the team wrote the module in the simplest way possible. It was only when the requirements did eventually change that the team changed the design of the module to be resilient to that kind of change.

One could argue that the team did only half the job. While the developers were protecting themselves from different input devices, they could also have protected themselves from different output devices. However, the team really has no idea whether the output devices will ever change. To add the extra protection now would be work that served no current puprose. It's clear that if such protection is needed it will be easy to add later. So, there's really no reason to add it now.

Following agile practices

The agile developers in our example built an abstract class to protect them from changes to the input device. How did they know how to do that? The answer lies with one of the fundamental tenets of object-oriented design.

The initial design of the Copy program is inflexible because of the direction of its dependencies. Look again at Figure 7-1. Note that the Copy module depends directly on the KeyboardReader and the PrinterWriter. The Copy module is a high-level module in this application. It sets the policy of the application. It knows how to copy characters. Unfortunately, it has also been made dependent on the low-level details of the keyboard and the printer. Thus, when the low-level details change, the high-level policy is affected.

Once the inflexibility was exposed, the agile developers knew that the dependency from the Copy module to the input device needed to be inverted, using the Dependency Inversion Principle (DIP) in Chapter 11, so that Copy would no longer depend on the input device. They then used the STRATEGY pattern, discussed in Chapter 22, to create the desired inversion.

So, in short, the agile developers knew what to do because they followed these steps.

1.

They detected the problem by following agile practices.

2.

They diagnosed the problem by applying design principles.

3.

They solved the problem by applying an appropriate design pattern.

The interplay between these three aspects of software development is the act of design.

Keeping the design as good as it can be

Agile developers are dedicated to keeping the design as appropriate and clean as possible. This is not a haphazard or tentative commitment. Agile developers do not "clean up" the design every few weeks. Rather, they keep the software as clean, simple, and expressive as they possibly canevery day, every hour, and every minute. They never say, "We'll go back and fix that later." They never let the rot begin.

The attitude that agile developers have toward the design of the software is the same attitude that surgeons have toward sterile procedure. Sterile procedure is what makes surgery possible. Without it, the risk of infection would be far too high to tolerate. Agile developers feel the same way about their designs. The risk of letting even the tiniest bit of rot begin is too high to tolerate.

The design must remain clean. And since the source code is the most important expression of the design, it too must remain clean. Professionalism dicates that we, as software developers, cannot tolerate code rot.




Agile Principles, Patterns, and Practices in C#
Agile Principles, Patterns, and Practices in C#
ISBN: 0131857258
EAN: 2147483647
Year: 2006
Pages: 272

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net