What is StampCoupling?
A web site, http://www.cs.unc.edu/~stotts/COMP145/coupling.html, says this: Two modules are stamp coupled if they communicate via a passed data structure that contains more information than necessary for them to perform their functions.
One particularly bad form of StampCoupling is when someone takes all of a program's global data, stuffs it into a structure, and then passes a pointer to the structure to every function in the program.
Can anyone explain what the term itself means? I.e., why is it called "stamp" coupling? I don't get it.
The story goes that back in olden times some shops made rubber stamps for key data structures. You could stamp it onto a program spec document and perhaps check some boxes off for which one the module should use as input or output. It's easier to pass the whole darned stamp to a subroutine than to make a new custom structure just for a single-use parameter. The COBOL copybook is a perfect stamp, too. I don't know if there really were physical rubber stamps or if the whole story is apocryphal. Anyone? JLS
But filtering only what is needed for a given module can create unnecessarily large interfaces. And, it violates OnceAndOnlyOnce under certain change scenarios. For example, assume a data structure has A, B, C, D, and E in it, and a particular module only uses A, C, and E. If we parameterize it, and later want to add item D, then we have to change the parameter list, but not a data structure.
Parameter-wise Before:
unit op(A, C, E) { ... }Parameter-wise After:
unit op(A, C, D, E) { <----- change spot 1 ... doSomethingWith(D) <----- change spot 2 ... }With Structure Before:
unit op(struc) { ... }With Structure After:
unit op(struc) { ... doSomethingWith(struc.D) <----- the only change ... }If passed in as a structure, there would only be one change-spot.
Also, this would suggest that passing objects is bad since a passed object likely has methods and/or attributes that a given unit may not need. (I am not an OO fan, but others here might be.)
I think it has to do with analysing the dependency of each module in a program. How do you know that changing removing struct.A from struct will not effect function sin(struct) - a function to calculate sine?
You don't. SoftwareDevelopmentIsGambling. One has to weigh the cost and impact of all estimated possible changes. Generally, if something is removed from a structure or table, it means a change in business rules that has to be studied on a case-by-case basis. Maybe there is a trade-off between maintenance ease and dependency insurance.
First, so what is the different between this and global variable? If we only use a global variable, don't you think it would be even easier? The code will be just
With Global Before:
unit op() { ... }With Global After:
unit op() { ... doSomethingWith(D) <----- the only change ... }And we don't violate once and only once because we don't even have to type the name of the struct variable in method signature!
Secondly, the whole purpose of making each class as small as possible is to ease maintenance. And I think dependency is one key of a maintainable program. You don't have to gamble in everything; you count the cards if you can.
Well, "context" is one of those never-ending factors to weigh. For example, every time you write a memo or email message to your colleagues or boss, you usually make some assumptions about the context so that you don't have to state everything explicitly from scratch. This keeps the messages shorter and to the point. In some ways, it is a form of DeltaIsolation. However, it does risk more misunderstandings. Code is not much different in my opinion. It may depend on the nature of the application or organization whether they want code to follow the pattern of informal email or a legal contract.
Some claim that databases more or less do what you suggest. There is a discussion about this in DatabaseNotMoreGlobalThanClasses.
That's why we should not use big struct with hold everything to many methods, because it contains too many context. It almost the same as global variable.
The alternative is detail coupling where interfaces are hard-wired to nitty gritty detail and have to change when the detail changes. It ain't no free lunch, it's a tradeoff of one kind of change impact for another kind. It's almost like traveling without a suitcase and carrying everything individually. TSA: "Sorry, you can't use a suitcase; that's stamp coupling!"
{Whether there is StampCoupling or not, interfaces are inevitably "hard-wired to nitty gritty detail and have to change when the detail changes." What StampCoupling describes is the situation where a single structure contains detail that is irrelevant to particular uses, and this causes coupling that would not otherwise exist. For example, a struct p {a, b, c, d, e} is passed to functions foo(p) and bar(p), but foo(p) only references p.a and p.b whilst bar(p) only references p.d and p.e. We say that foo() and bar() are "stamp coupled" by the "stamp" p. Better would be to define struct r {a, b} and q {d, e} and redefine foo(p) as foo(r) and bar(p) as bar(q). Thus, foo and bar would no longer be stamp coupled by the "stamp" p.}
But level of "irrelevant" changes over time. The goal is to simplify maintenance, not necessarily weed out all short-term irrelevancies for the heck of it. One is weighing tradeoffs. I've seen both approaches cause problems and the best choice is that one that best fits actual future changes in terms of code maintenance time and quality. I generally use SimulationOfTheFuture to make such decisions. -t
{Yes, there may be justifications for maintaining StampCoupling -- e.g., foo(p) will soon be changed to reference p.c, p.d and p.e, whilst bar(p) will soon be changed to reference p.a, p.b and p.c, and it's clear that the members of struct p are all closely coupled. StampCoupling warrants removal when it's clear that struct p {a, b, c d, e} was created arbitrarily, and its members are not all closely coupled.}
Well, okay, we at least seem to be agreeing that the value (good/bad) of StampCoupling depends on how future changes play out.
{StampCoupling is a characteristic of code, so it doesn't depend on future changes. Whether it is tolerable or not may depend on the predicted likelihood of future changes.}
I reworded the prior sentence.
I don't think Stamp Coupling implies that passing objects is bad, rather it only targets a specific type of object. It refers to "data structures" that lack any knowledge of what it is responsible for beyond storing and accessing it's own data. In other words, it is a poor object that leads to low cohesion in the system. Stamp coupling does not refer to "good objects". So if a fully mature object is being passed around, this is probably not Stamp Coupling but if a dumb data structure (ie linked list) is being passed around that other collaborators must do extra work with, this is probably Stamp Coupling.
The problem is that this is a circular kind of argument. It depends on the notion of "poor object" versus "good object"/"fully mature object", which you do not define even a little bit. The point of StampCoupling and CouplingAndCohesion in general is to pin down precisely what is a good versus a bad object.