Insidious Tight Coupling

Insidious tight coupling, where one module depends on another unknown to the compiler, can lead to overly complex development.


January 10, 2007
URL:http://www.drdobbs.com/architecture-and-design/insidious-tight-coupling/196802793

Bil has written three books on multithreaded programming, along with the GNU Emacs Lisp manual and numerous articles. He is currently a professor at Tufts University and can be contacted at [email protected].


Coupling is a rough measure of how much one software module relies on another. In particular, "loose coupling" refers to a module that uses only a published interface to another module. Tight coupling, on the other hand, depends on internal details.

Tight coupling is a bad thing because it means that every time someone decides to change the implementation of his or her module, every tightly coupled module might have to be changed, too. For instance, in Example 1(a) a loosely coupled module will only use getAge(). Should a module "cheat" and access the age instance variable directly, then when Example 1(b) is distributed, the cheating module will no longer even compile.

(a)

class Person { 
   int age;
   public int getAge() {return age;}
}

(b) 

class Person { 
   int birthYear;
   public int getAge() {return thisYear - birthYear;}
}

Example 1: (a) Tight coupling; (b) a failure waiting to happen.

Loose Is Better, Always

What you often hear about is the necessity to couple more tightly "for performance reasons." Indeed, it is not difficult to come up with examples of where tight coupling lets naïve compilers produce significantly better code—but you shouldn't use naïve compilers for production code.

The one good thing about this kind of tight coupling is that the compiler prevents you from distributing nonworking code by accident. Which brings us to insidious tight coupling (ITC).

ITC is the situation where one module depends on another module having some special state, or set of string literals, but where the compiler doesn't know. In this case, it is easy to make changes to one module and miss another module that was making assumptions about the first.

An example would be where you know that students are to be entered into a list in grade-point order. If another module relied upon this, then it would fail should the students be entered in a different order. The appropriate way to deal with this situation is to make your code reflect the requirements you put upon it. In this case, you would subclass List with a class SortedList, which would throw an exception if someone tried to insert a student out of order.

The most common example of ITC is when two modules need to use the same string literal. Within a single language project, this issue is resolved through the use of canonical objects and constants. But the trouble starts when those modules are in different languages. There's no compiler to tell you when you misspell a variable name, or that you only changed six of the seven places a literal was used. Moreover, because the typical use of these strings is for table lookups (request.getParameter("firstName"), for example), a null value might be a legal value, making it much more difficult to find the problem. (Often times, the bug is never noticed and the program simply spits out incorrect data.)

This is ITC at its worst. And where do you typically see many languages coming together? Web apps!

For instance, consider:

That's 11 languages for an elementary application. This is insane. What the heck are we doing? Well, we're letting tools control our lives. We are designing our applications so that they'll fit neatly into Ruby or Tapestry or J2EE. We have all these fancy tools that work for one aspect of creating a web application, then leave us to muddle through the rest. Tools are supposed to serve us. We are supposed to decide what we want, then find the tools that let us do it. We can write software to do anything. Why don't we write software to build entire web applications?

What Do We Want?

All too often, we start by asking what a tool can do for us, when we should be asking what we want our app to do. We want our apps to display information to users, accept input, execute transactions, and display more information. Figure 1 is a transition graph in classic MVC format illustrating this point. The application starts by displaying the LoginPage, which transitions to either the VerifyLoginController (passing an ID and password) or to a NewVoterController (passing nothing). If the login is valid, the StatusPage is displayed, showing details about the Voter and elections voted in. If not, it's back to the LoginPage with an error message.

[Click image to view at full size]

Figure 1: A transition graph in classic MVC format.

It appears that most infrastructures and most web designers think in terms of page-to-page transitions. ("The LoginPage can transition to either the VerifyLoginPage or the NewVoterPage.") This is unfortunate because page-to-page transition graphs leave out essential information and result in horrendous hacks ("forwarded" pages, anyone?) to get around perceived problems.

Languages at Different Levels

Java is a lousy language for describing page layout, and HTML is no good at logic. So we do want to use several different languages to define our applications. Can we do it and still avoid ITC? There are four basic levels I see for web apps, each of which is best done with its own language:

In this fashion, there is a single source for all the name strings and the generator, plus the compiler guarantees that no naming mistakes are made. The tricky part of dealing with multilevel designs like this is ensuring that changes in one language (say, the transition graph) are appropriately reflected in the generated code; and you must prevent programmers from changing the generated portions of the code to conflict with the graph. But this is an issue for tool designers, not application programmers.

What about those 11 languages? Don't they serve a necessary purpose? Yes. You just don't want application programmers to have to deal with them directly. You want them to be treated the same way we treat assembly code—as something that's generated for us. It's great to have web pages do local field validation. It's just that the validation code should be generated from the data description for us. The same goes for just about everything else in that list.

Conclusion

It's all about writing readable code. A quick glance at the transition graph is sufficient for us to understand the flow and information requirements of the application. With all the stub code being generated for us, the only Java code we have to write is the code that does the actual logic. Contrast this to poring through the hundreds of lines of XML, JSP, HTML, Java, and JavaScript code required by J2EE, Spring, and the like, to do the same thing. By eliminating insidious tight coupling, you are able to better understand what your actual objectives are, and extensively simplify the process of creating applications, web-based or otherwise.

For More Information

Meyers, Glenford J., The Art of Software Testing, John Wiley & Sons, 1979.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.