I am reading Ousterhout’s A Philosophy of Software Design. In Section 2.3, Outserhout writes:
The signature of a method creates a dependency between the implementation of that method and the code that invokes it: if a new parameter is added to a method, all of the invocations of that method must be modified to specify that parameter.
Does a method really create a dependency between the calling code and its implementation?
I see how the calling code becomes dependent on the signature, but surely the implementation is arbitrary; it could be unimplemented (ignoring the parameters entirely), and therefore not dependent on the signature at all.
Perhaps the author meant that the dependency exists spacially between the calling code and the implementation? I could agree with this, but I suspect the truth of this idea depends largely (perhaps entirely) on your personal model of how these ideas fit together.
5
To give your question more context, let us remember what Ousterhood is trying to explain in the section of the book. Let me cite the first sentence from the paragraph where you got your cite from:
For the purposes of this book, a dependency exists when a given piece of code cannot be understood and modified in isolation; the code relates in some way to other code, and the other code must be considered and/or modified if the given code is changed.
Then he gives a few examples, and one example is “two pieces A and B of code, where A calls a method from B”.
Now to your question:
Does a method really create a dependency between the calling code and its implementation?
Yes, it does: the calling code becomes dependend on the method – on it’s signature as well as on it’s implementation.
-
When the signature changes, you clearly have to change the callers.
-
When the implementation changes, you may have to test if caller(s) now behave differently, maybe you have to change them as well, maybe not, but at least you have to consider it.
This is an unidirectional dependency: the called method does usually not become dependend on the caller.
I see how the calling code becomes dependent on the signature, but surely the implementation is arbitrary; it could be unimplemented (ignoring the parameters entirely), and therefore not dependent on the signature at all.
First, this is not what Ousterhout wrote. He did not say “the implementation of a method becomes dependend on its signature”, I think you have misread that statement. Nevertheless, this is usually true, the implementation of a method depends typically on its signature. Sure, there are edge cases where parameters are superfluous and an implementation will not have to be changed when a method’s signature changes, but at least one has check whether this is the case or not.
Note the chapter is about “Causes of complexity”, which gives some examples how complexity is introduced into a software system by dependencies on a broad scale. Saying the “signature creates the dependency” is IMHO just a figure of speech, I would not read this too literally. The dependency is created by the caller calling the callee, and when we read the calling code, we identify the dependency by what we see from the callee: its signature.
11
The implementation includes the name, the parameter list, the return type, as well as the body of the method. Ignoring them in the body does not change the requirement that all the parameters are mentioned.
9
The key word you’re looking for here is “contract” (or “interface”, although this one is a bit more ambiguous for languages that have an interface
keyword).
When two components interact with one another, they would ideally remain mostly independent of one another, each one being independently capable of changing its internal implementation without the other needing to be made aware of it. This is what we try to achieve using encapsulation and general clean coding.
However, there is an inevitable link between the two. Their interaction inherently means that one will call on the other in some way, using some syntax. Though it’s not the only way, let’s assume their interaction is that A calls one of B’s methods.
In other words, somewhere in the A
logic you will find myB.DoSomething(foo);
. I can change the implementation of A, e.g. only calling this method in certain situations, without it impacting B’s implementation of the DoSomething
method itself. Similarly, I can change B’s implementation of the DoSomething
method, without that changing anything about A’s logic (i.e. deciding when to call the method).
However, when I change the signature of the method, this inherently means that both A and B have to respond to this change. There’s no way around that. The interaction is inherently shared between the two interacting components.
but surely the implementation is arbitrary; it could be unimplemented (ignoring the parameters entirely), and therefore not dependent on the signature at all
If you’re going to ignore the newly added parameters, there’s no point to adding these parameters in the first place. You’re trying to come up with nonsensical edge cases to the advice.
However, in the interest of humoring your quest for edge cases:
- If the added parameter is an optional one (e.g. C# allows for this), then it’s possible that the consumer does not need to update their call.
- If you change a method parameter’s type in a way that the prior type can be implicitly casted to the newer type (e.g.
int
tofloat
), the consumer does not need to update their call either.
That being said, if you’re trying to learn things, I suggest you focus on what’s being conveyed rather than jumping straight into looking for edge cases. Almost every rule has an edge case or exception, and while it is possible to define things in a way to account for more edge cases, this significantly complicates the learning material, which is detrimental to how easy it is to learn the core concept.
16
The signature of a method creates a dependency between the
implementation of that method and the code that invokes it: if a new
parameter is added to a method, all of the invocations of that method
must be modified to specify that parameter.Does a method really create a dependency between the calling code and
its implementation?I see how the calling code becomes dependent on the signature, but
surely the implementation is arbitrary; it could be unimplemented
(ignoring the parameters entirely), and therefore not dependent on the
signature at all.
Consider the following (more clear) scenario of adding an extra parameter in the API. And having several separate implementations. Then it is clear that every implementation is dependent on the API.
Just this is meant, and using the scenario above instead of considering a signature and an implementation vaguely, abstractly, might be too unclear.
Consider also the solution: instead of adding an extra parameter to a signature that maybe already has several parameters, use a compound parameter type with fields.
Then the parameter type (class) will be extended by an extra parameter.
But usage and signature will not be changed. Implementation(s) can be tackled separately, as can be the callers by an additional type constructor.