Second uncle Bob believes that the problem is in design. I will also go back one step and check the design of your contracts .
In short
instead of saying "return -1 for x == 0" or "throw CannotCalculateException for x == y", underspecify niftyCalcuatorThingy(x,y) with the precondition x!=y && x!=0 in appropriate situations (see . below). Thus, your stubs can behave arbitrarily for these cases, your unit tests should reflect this, and you have the maximum modularity, that is, the freedom to arbitrarily change the behavior of your system under the test for all unproven cases - without the need to change contracts or tests .
Designation, if necessary
You can distinguish your statement “-1 when it for some reason doesn’t work” according to the following criteria: Is the script
- exceptional behavior that an implementation can test?
- in the method / liability domain?
- an exception that the caller (or someone previously in the call stack) can recover from / handle in some other way?
If and only if 1) - 3), specify the script in the contract (for example, that EmptyStackException is EmptyStackException when pop () is called on an empty stack).
Without 1) an implementation cannot guarantee specific behavior in an exceptional case. For example, Object.equals () does not indicate any behavior when the condition of reflexivity, symmetry, transitivity, and consistency is not satisfied.
Without 2), the SingleResponsibilityPrinciple fails, the modularity is broken, and users / code readers are confused. For example, Graph transform(Graph original) should not indicate that a MissingResourceException can be MissingResourceException because cloning through serialization is performed in depth down.
Without 3) the caller cannot use the specified behavior (specific return value / exception). For example, if the JVM throws an UnknownError.
Advantages and disadvantages
If you indicate cases where 1), 2) or 3) is not fulfilled, you get some difficulties:
- The main purpose of the contract (design) is modularity. This is best implemented if you really share responsibilities: when the precondition (the responsibility of the caller) is not met, not specifying the implementation behavior leads to maximum modularity - as your example shows.
- you don’t have any freedom to change in the future, even for the more general functionality of a method that throws an exception in fewer cases
- exceptional behavior can become quite complex, so the contracts that cover them become complex, error prone and difficult to understand. For example: is each situation covered? What behavior is true if several exceptional prerequisites are met?
The disadvantage of the lower specification is that (test) reliability, that is, the ability of an implementation to respond appropriately to abnormal conditions, is more complex.
As a compromise, I like to use the following contract scheme where possible:
<(Semi-) formal PRE- and POST-condition, including exceptional behavior where 1) - 3). >
If PRE fails, the current implementation returns RTE A, B, or C.