Chapter 4. Shalloway’s Law and Shalloway’s Principle

A few years ago someone in one of my1 design patterns classes mentioned I should name something after myself since I had written a successful book on design patterns. I, of course, liked this person and his idea immediately. So, I went about thinking about what would be appropriate. The best I could come up with was the following:

When N things need to change and N>1, Shalloway will find at most N-1 of these things.

Although I had hoped to find something complimentary, this was the most appropriate thing I could come up with. I point out that I didn’t ask for this when I was born; I was given this “ability.” Most people also have this trait. In other words, this isn’t choice; it’s how we are. This means we had better pay attention to it. Otherwise, we’ll find that if we write code that requires finding more than one thing, we won’t find them all, but our customers (or if we’re lucky, someone else on our team) will.

Although I am not particularly proud of Shalloway’s law, I am proud of Shalloway’s principle, which I came up with to deal with it. Shalloway’s principle states the following:

Avoid situations where Shalloway’s law applies.

Kent Beck’s famous “once and only once rule” is one approach to this—in other words, keep N at 1—but not the only one. Although avoiding redundancy is perhaps the best way to follow Shalloway’s principle, it is not always possible. Let’s begin by looking at different types of redundancy and see how we might avoid them, or if not, how we can still follow Shalloway’s principle.

Types of Redundancy

If we look at two particular types of redundancy, we can see that although some forms of this pathology are easy to see, others are more subtle. This is important because all forms of redundancy violate Shalloway’s principle and create maintenance problems.

Copy and Paste

This is the most obvious type of redundancy and probably the easiest to avoid. Using functions is a common way to avoid this. Rather than copying code from one place to another, we move the code into a function or service class where it can be reused from both places.

Magic Numbers2

Using magic numbers as redundancy is not quite as obvious as copy and paste, but it is redundancy. Basically, the redundancy comes from the fact that the meaning of the magic number must be known everywhere the magic number is used. How to avoid magic numbers is well-known—just use #defines or consts or their equivalent, depending upon your language of choice.

Other Types

There are, of course, many other types of redundancy. These include redundant behavior (for example, “save to file” or “save to database”), redundant information, and redundant implementations. We’re not going to list them all here. It is important to realize that any time a concept (data, algorithm, code, and so on) appears in more than one place, there is redundancy.

Redefining Redundancy

Redundancy can be much more intricate than what people initially think. The definition of redundancy I am referring to here is “characterized by similarity or repetition.”

I suggest redundancy can be fairly subtle, and defining it as duplication or repetition is not sufficient. Defining it as similarity, unfortunately, can be a bit vague—so perhaps it isn’t that useful either. I propose a definition of redundancy in code that I believe is very useful.

Redundancy is present if when you make a change in one place in your code, you must make a corresponding change in another place.

A little reflection will tell us that redundancy, at least defined this way, is almost impossible to avoid. For example, a function call has redundancy in it. Both the calling and defined statement must be changed if either change. From this we can also see the relationship between redundancy and coupling. And, as with coupling, not all redundancy is bad or even avoidable. I would say the type of redundancy you must avoid is that redundancy that violates Shalloway’s principle.

Redundancy that doesn’t violate Shalloway’s principle is likely to be a nuisance at most. For example, in the earlier case, I can have a function called from any number of places. Doing so makes my system have a significant amount of redundancy. However, this doesn’t violate Shalloway’s principle. Why? If I change the defining statement, the compiler will generate a to-do3 list for me to change my calling statements. I still, of course, have work to make my changes, but that is considerably different from the dangerous situation I would be in if I had to also find the changes that were required.

Other Types of Redundancy

Given our new definition of redundancy, what are other common forms of it (and how do we avoid them)? Implementations are often redundant even if the code making them up are not duplicates of each other. For example, if a developer takes a function and copies it (clearly redundant at this point) but then changes all the code (presumably removing the redundancy) because the implementation of the new function is different, do you still have redundancy? I would suggest you do—not of the implementation but most likely the algorithm you are implementing. The second function was copied from the first one presumably because the flow of both algorithms was the same; only their implementations were different.

How do you remove this type of redundancy? I’ll refer to Chapter 19, The Template Method Pattern, from Design Patterns Explained: A New Perspective on Object-Oriented Design. Basically, it involves putting the algorithm in a base (abstract) class and having the implementations of each step be in derived (extended) classes.

The Role of Design Patterns in Reducing Redundancy

We often say that the purpose of design with patterns is to handle variation. Many patterns are readily identified as doing this:

• Strategy handles multiple algorithms.

• Bridge handles multiple implementations.

• Template Method handles multiple implementations of a process.

• Decorator allows for various additional steps in a process.

Most of the design patterns in the seminal work Design Patterns: Elements of Reusable Object-Oriented Software (Gamma, Erich, et al. Boston: Addison-Wesley, 1994) are about handling variations directly, or they enable the handling of variations.

Another way to think of design patterns is that they eliminate the redundancy of having to know which implementation is being used.

Because design patterns handle variations in a common manner, they can often be used to eliminate redundant relationships that often exist in a problem domain. For example, a purchasing/selling system will have several types of documents and payment types. Each document type may have a special payment type, but the relationship between them is probably similar to the relationship between any other pair. This sets up redundant relationships. By using abstract classes and interfaces, redundancies can be made explicit and allow the compiler to find things for you. For example, when an interface is used, the compiler will ensure that any new method be defined in all cases—you won’t have to go looking for them.

Few Developers Spend a Lot of Time Fixing Bugs

A common misconception among software developers is that they spend a lot of time fixing bugs. But on reflection, most realize that most of their time is spent finding the bugs. Actually fixing them takes relatively little time. One of the reasons people spend a lot of time finding bugs is that they have violated Shalloway’s principle. If you can’t find the cases easily, bugs will result.

A key to avoiding this problem is to be aware of when you are violating Shalloway’s principle. Here’s an interesting case. Let’s say you’ve been using an Encrypter class in your code. If you’ve been following our suggestion of separating use from construction, you may have code that looks something like the following:

public class BusinessObject {
  public void actionMethod() {
    AnotherObject aAnotherObject= AnotherObject.getInstance()
    String aString;
    String aString2;

    // Other things
    Encrypter myEncrypter= Encrypter.getEncrypter();

    //
    myEncrypter.doYourStuff( aString);

    //
    aAnotherObject( myEncrypter);


    //
    myEncrypter.doYourStuff( aString2);

  }
}

public class AnotherBusinessObject {
  public void actionMethod( Encrypter encrypterToUse) {
    // Other things
    //
    //
    encrypterToUse.doYourStuff( aString);
  }
}

Now let’s say a case comes up where we don’t need to use the Encrypter. We might change the code from

// Other things
    Encrypter myEncrypter= Encrypter.getEncrypter();

to the following:

Encrypter myEncrypter;
If (<<need an encrypter>>)
  myEncrypter= Encrypter.getEncrypter();

Then, of course, we have to go through our code and see when we don’t have an encrypter.

if (myEncrypter != null)
  myEncrypter.doYourStuff( aString);

At some point we’ll hit the second case of this. This means Shalloway’s law is in effect. By the way, a corollary to Shalloway’s law is that “If you find two cases, know you won’t find all of the cases.” At this point, we should figure out a way not to have to test for the null case. An easy way is to put the logic in the getEncrypter method in the first place. In other words, have Encrypter’s getEncrypter method consist of the following:

If (<<don't need an encrypter>>)
  Return null;

This, first of all, keeps all the knowledge about the construction of the encrypter out of the calling class. It also eliminates the need to check for the null condition—both avoiding Shalloway’s law and decoupling the client code from the Encrypter object.

This, by the way, is the Null Object Pattern. I suggest that any time you find you are doing a test for null more than once, you should see whether you can use this properly.

I suspect that many readers will think this example is somewhat contrived because with a factory making the Encrypter object, it is pretty clear that the test for a null case should be handled there. But this is also my point—when you separate use from construction, you are more likely to make better decisions later. If getEncrypter wasn’t being used and the client code had the rules of construction, setting the myEncrypter reference would likely never occur.

Redundancy and Other Code Qualities

It’s useful to note how redundancy is related to other code qualities, in particular, coupling and testability. Any time you have redundancy, it is likely that if one of the occurrences changes, the other one will need to change. If this is the case, these two cases are coupled. Coupling and redundancy are often different flavors of the same thing.

Note that redundancy also raises the cost of testing. Test cases can often be reduced if redundant relationships are avoided. Let’s consider the case shown in Figure 4.1. Note that each of the service objects is doing conceptually the same thing but is doing it in a different way (for example, different kinds of encrypting).

Figure 4.1 Testing in a one-to-many relationship

image

Note that we need to have the following for a full set of tests:

• Test of Service1

• Test of Service2

• Test of Service3

Client using Service1

Client using Service2

Client using Service3

The need for testing Client using the services is because we have no assurance that we’ve abstracted out the service code. There may be coupling taking place, especially since each service interface may be different. Notice what happens when more clients become involved—this gets worse and worse.

Now, consider what happens if we make sure that all the service objects work in the same way. In this case, we basically abstract out the service objects. If we put in an abstraction layer (either an abstract class or an interface that the services implement), we get what is shown in Figure 4.2.

Figure 4.2 Creating a one-to-one relationship

image

Although we still need to test each Service, we now need to test only the Client to Service relationship. Note that as we get more client objects the savings are even greater.

Summary

Shalloway’s law is both a humorous attempt at saying avoid redundancy and some guidance for developers in how to do so—or at least to make it less costly not to do so. Understanding redundancy is key to Shalloway’s law, and avoiding the cost of it is the essence of Shalloway’s principle.

A powerful question when programming that can be deduced from all of this is “If this changes, how many places will I have to change things, and can the compiler find those for me?” If you can’t see a way to make it so the answer is either “1” or “yes,” then you have to acknowledge that you have a less than ideal design. At this point, you should consider an alternative—or, heaven help you—ask someone else to suggest an alternative.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.54.108