4

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
  • 1
    Not in JavaScript…
    – jmoreno
    Commented Apr 23 at 11:52
  • 1
    "a dependency between a and b" does not say the same as "a dependency of a on b" / "… from a to b", it does not specify a direction.
    – Bergi
    Commented Apr 23 at 19:36
  • 2
    I suspect Ousterhout just use "implementation" with a different meaning than what is usual. He uses implementation to mean the definition of the method as opposed to calling the method. The signature might be defined on an interface with no implementation at all in the usual sense.
    – JacquesB
    Commented Apr 24 at 5:19
  • 4
    I think the author made a poor choice of words. The signature of a method is to a method as an interface is to a class, so client code (callers) and the method's implementation are both dependent on the signature (really, it's dependency inversion at the function level). A good design is one that takes advantage of that, buy conceptualizing the method in some way that makes the signature more stable with respect to (some set of) changes then either the client code or the underlying implementation. Commented Apr 24 at 16:46
  • 1
    "I see how the calling code becomes dependent on the signature, but surely the implementation is arbitrary" - i only now noticed this bit. Yeah, technically, implementation can be pretty arbitrary, but most implementations will make some use of the parameters, and rely on their meaning (and that of the return type). If you already have an impl. that uses the params, you will have to change it if you arbitrarily change the param list - so, it depends on it. E.g., suppose you have a func that makes a rectangle, with params (x, y, width, height), and change that to (left, top, right, bottom). Commented Apr 25 at 22:26

4 Answers 4

12

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
  • 2
    "the calling code becomes dependent on the method - on it's signature as well as on it's implementation" - I would disagree with that. We write software with method calls precisely so that we do not depend on a particular implementation, the interface (type of the object, which includes the method signatures but not a concrete implementation) is an abstraction layer and a module boundary. Admittedly, the interface that we depend on also includes contracts (expectations) about what the method should do, which are seldom formalised.
    – Bergi
    Commented Apr 23 at 19:44
  • 1
    @Bergi: I added some more context from Ousterhood what he is speaking about, then it might become clearer. Fact is, when code A calls a method from B, and B is changed in some way (interface or implementation), one has to consider that A might be influenced by the change in some way. Sometimes this is wanted, sometimes not, still the only guranteed way in A not becoming dependend on B is not to call the method at all.
    – Doc Brown
    Commented Apr 24 at 5:22
  • 1
    @Flater: still a dependency exists when A calls B, and any change in B has some potential to influence the behaviour of A, maybe in a wanted, maybe in an unwanted manner. It is only a simplistic example what Ousterhood means by "dependency" in the context of that book, don't overinterpret it.
    – Doc Brown
    Commented Apr 24 at 5:42
  • 2
    @Flater, "For a method, its contract is its signature" - I don't agree with this. The method signature is only a narrow aspect of what a programmer would regard as the "contract". For example, if string String.ToUpper(string) suddenly started returning lowercase due to an implementation change, a programmer would regard the "contract" as different, even though the signature needn't change.
    – Steve
    Commented Apr 24 at 8:16
  • 1
    @Steve The quoted text in the question is not focusing on behavior, it is focusing on the blast radius of a change to the method signature. The cleanliness of code, which is the ultimate goal of the piece being discussed, is irrespective of the precise business rules. That's not to say that we as developers don't need to consider both, we obviously do as marrying the two is arguably the core premise of our role, but that doesn't mean that a guideline on code cleanliness needs to explicitly focus on application behavior at the same time.
    – Flater
    Commented Apr 25 at 1:30
1

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
  • Can you expand on the idea that 'the implementation includes the parameter list'? Why does it? Does the implementation also include the return type? Commented Apr 23 at 10:09
  • @user3899725 yes, it includes all of those. plus the name. How would it not include those things?
    – Caleth
    Commented Apr 23 at 10:12
  • Well, I am testing the idea that the implementation is dependent on the parameter list. Because the implementation includes parameters (I am not convinced that it does, but let's assume that is true), does that necessarily mean it is dependent on them? For example: if we say the return type is included in the implementation - and is therefore dependent on the return type - what if the implementation simply throws an exception and does nothing else. Is the implementation necessarily dependent on the parameters, return type, etc. simply because they are 'included'? Commented Apr 23 at 10:16
  • @user3899725 no, the implementation is all of the things I mentioned. Callers are dependant on the name, parameters and return type, and to some extent the semantics of the body. And in most languages, the return type is implicitly Type or thrown Exception or never finish, not just Type
    – Caleth
    Commented Apr 23 at 10:19
  • 3
    So implementation = signature + body? If this the case, and since there is no dependency between the body and the caller, the author should have said that the signature creates a dependency between the signature and the calling code, which is a weird thing say since it is true by definition. Commented Apr 23 at 16:48
0

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 to float), 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
  • Where is comprehensive information about the "contract" stored? Is it something in source code? In separate documentation? In your mind?
    – Steve
    Commented Apr 24 at 6:28
  • @Steve: I'm not quite sure what your specific question is, and whether the seemingly facetious tone is intentional.
    – Flater
    Commented Apr 24 at 6:34
  • 3
    I disagree that "contract" is the same as "interface". I would say "contract" = "interface" + pre-conditions + post-conditions. Commented Apr 24 at 6:56
  • I'm not intending to be facetious, but I am hitting where I think it is going to hurt, because I think this notion of a "contract" is generally an ill-defined thing that exists largely in the mind of the beholder (i.e. it's not easy to thoroughly record, or share), and I also think that the vast majority of reasons why you would want to change an implementation are the same reasons why the "contract" itself would be changed on the same occasion.
    – Steve
    Commented Apr 24 at 6:59
  • @Steve Like many things, different contexts lead to different definitions. For the compiler, the contract is nothing more than the signature of a method call. For a consumer, it's the expectation of behavior. For an API, it's the combination of model structure and endpoint signature. The context of the quoted piece is one of encapsulation of the code (or lack thereof) whereby changes to the contract impact both the implementer and the consumer. Behaviour is irrelevant for the specific point being made.
    – Flater
    Commented Apr 25 at 1:24
0

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.