Domino Changes in Static Typing

David Buck
April 22, 2003

One of the central principles of object oriented programming is Encapsulation. Encapsulation states that the implementation details of an object are hidden behind the methods that provide access to that data. But why is encapsulation a good idea? Why bother to do it in the first place? Just stating that it's "good OO design" isn't sufficient justification.

There is one primary justification of encapsulation. It's a principle I call "Local Change - Local Effect". If you change code in one spot, it should only require changes in a small neighborhood surrounding the original change. When used properly, encapsulation allows software to change gradually without requiring bulk changes throughout the system. This is critical for maintenance programming. If you use an agile development methodology like XP, then you are always in maintenance mode.

I get a knee-jerk reaction, therefore, when I see language designs that require wide-sweeping propagation of changes just to make one simple design change in the system. Often, these changes have a domino effect where one change requires other changes and those in turn require others. The end result could be a tremendous amount of work.

I ran into one such case when developing C++ code. I had flagged one method (member function in C++) as const. This means that the method doesn't change any instance variables of the receiver. Over time, I had refactored the member function into several member functions that called each other several levels deep and all declared as const.

I then noticed that the lowest-level function was performing a calculation time after time. I decided that it would be more efficient to calculate it once and cache the answer in the object. By doing this, however, I changed the "const" nature of the function and I had to remove the const declaration. When I did this, the callers of this function failed to compile because const functions are not allowed to call non-const functions. I had to follow the whole tree of calls removing const declarations.

In theory, this propagation could have had a domino effect through the whole system. On large systems, this kind of propagation could make it impractical to change the code.

A similar problem exists in Java's exception handling. Java requires that the programmer specially declare methods that raise exceptions by using a "throws" clause. In addition, any callers of those methods must either handle the exception or declare that they throw it.

Now, consider what happens when you need to change low-level code to throw an exception. Typically, these exceptions are caught at a high level by the application. In order to add the throw at the low level, all methods called between the throw and the catch must be changed to state that they throw the exception. To make matters worse, it's not only that one chain from the throw to the catch that must be changed. The changes branch out into trees of callers and could span many classes throughout the system. If any of those classes happen to be general utility classes or belong to other development teams, it may be impractical to change all users and it would therefore make the addition of the exception infeasible.

Why is Smalltalk with its dynamic typing less susceptible to these problems? Dynamic typing allows the programmer to do the right thing without trying to convince the compiler that it's the right thing. Static typing requires the programmer to provide additional information to the compiler so it can double check the code. This additional information gets in the way when the programmer needs to make changes to the system. Not only does the code itself have to be changed, but often the types have to be changed. When you change types, you run the risk of having the domino effect. By changing a type here, you have to change it in many other places and changing it in those places requires changes in still other places and so on. All these propagating changes can be so involved that it makes the original change infeasible.

Many of you I'm sure, will argue that by doing this extra checking, your program is more likely to work correctly. I've never found that to be the case. There are many different kinds of errors that can arise in software and type errors are only one small category. Static typing will never eliminate divide by zero errors, arrays indexed out of bounds, nil values instead of pointers, deadlock, starvation, race conditions or infinite loops. The only way to find these problems is by thorough testing. When you do thorough testing, type errors are very quick to find and fix.

So the next time you see domino-effect changes occurring in your software, ask yourself why it's happening and whether something in the system is breaking the "local change - local effect" rule. Chances are it's your static type system.