Should methods have the same preconditions as the methods they call? - code-contracts

I've recently had a few scenarios where small changes to code have resulted in changing preconditions across multiple classes and I was wondering if design by contract is supposed to be that way or not.
public Goal getNextGoal() {
return goalStack.pop();
}
If goalStack.pop() has a precondition that the stack isn't empty, then does getNextGoal() need to explicitly have the same precondition? It seems like inheriting the preconditions would make things brittle, and changing to a queue or other structure would change the preconditions to getNextGoal(), it's callers, and it's callers' callers. But it seems like not inheriting the preconditions would hide the contracts and the callers, and the callers' callers, wouldn't know about the preconditions.
So brittle code where all callers know and inherit the preconditions and postconditions of the code they call, or mysterious code where callers never know what the deeper preconditions and postconditions are?

It depends on what your calling method does exactly. The important thing with preconditions is that the caller is responsible for fulfilling the preconditions.
So if callers of your GetNextGoal method should be responsible for providing a non-empty stack, then you should indeed also set preconditions on your GetNextGoal method. Clarity of preconditions is one of the huge advantages of Code Contracts, so I'd suggest you put them in all places where callers have to fulfill the preconditions.
If your code seems brittle however, it might be a sign that you need to refactor some code.
It seems like inheriting the
preconditions would make things
brittle, and changing to a queue or
other structure would change the
preconditions to getNextGoal(), it's
callers, and it's callers' callers.
If you expose the queue to the callers and change it later ( to another structure, like you said ), it's callers would also have to change. This is usually a sign of brittle code.
If you would expose an interface instead of a specific queue implementation, your preconditions could also use the interface and you wouldn't have to change the preconditions every time your implementation changes. Thus resulting in less brittle code.

Exceptions are one solution but perhaps not feasible for your situation.
Documenting what happens if there are no goals is normal.E.G. This is what malloc() does in C
I can't tell if you are using Java or C++ or something else as each language might have slightly more natural ways for that specific language.

Related

Are mocks and stubs implementation details?

I have read that with TDD we should approach the entity (function, class etc.) under test from the perspective of the user/caller of the entity. The gist being focusing on the public "interface". This in turn would drive the design and help reason about the design earlier.
But when we need to introduce mocks and stubs into our tests, isn't that an implementation detail?
Why would/should the "user" care about the other entities that are supposed to be there?
E.g.
How to start writing a test for the PlaceOrder service which should check with the credit card service if the user has enough money? Putting a mock for the credit card service whilst writing a test from the perspective of the PlaceOrder client looks out of place now - because it is an implementation detail; our PlaceOrder may call the credit card for each user or it can simply have a cache with scores provided at the creation time.
It's not clear-cut. As a catch-phrase says: Tests are specifications.
When you use Test Doubles you are, indeed, specifying how your System Under Test (SUT) ought to interact with its dependencies.
I agree that this is usually an implementation detail, but there will typically be a few dependencies of a more architectural character.
A common example is an email gateway. If your SUT should send email, that's an observable side effect that the user (or some other stakeholder) cares about.
While you can (and perhaps should?) also run full systems tests that verify that certain conditions produce real emails that land in certain real mailboxes, such test cases are difficult to automate.
Inserting a Test Double that can take the place of an email gateway and verify that the correct message was delivered to the gateway is not only an implementation detail, but an important part of the overall system. In such cases, using a Test Double makes sense.
Yes, Test Doubles specify behaviour, but sometimes, that's exactly what you want.
How much you should rely on this kind of design is an architectural choice. In addition to sending emails, you might choose to explicitly specify that a certain SUT ought to place a message on a durable queue.
You can create entire systems based on asynchronous messaging, which could imply that it'd be architecturally sound to let tests rely on Test Doubles.
In short, I find it a useful heuristic to use Test Doubles for architectural components, and rely mostly on testing pure functions for everything else.
For something like an order service, I wouldn't let the order service contact the payment gateway. Rather, I'd implement the order service operations as pure functions, and either pass in a payment token as function arguments, or let the output of functions trigger a payment action.
The book Domain Modeling Made Functional contains lots of good information about this kind of architecture.
On the other hand, the book Growing Object-Oriented Software, Guided by Tests contains many good examples of how to use Test Doubles to specify desired behaviour.
Perhaps you'll also find my article From interaction-based to state-based testing useful.
In summary: Tests are specifications. Test Doubles are specifications. Use them to specify the observable behaviour of the system. Try to avoid using them to specify implementation details.
But when we need to introduce mocks and stubs into our tests, isn't that an implementation detail?
Yes, in effect. A bit more precisely, it is additional coupling between your test and the details of your test subject's implementation.
There are two ideas in tension here. On the one hand, we want that our tests are as representative as possible of how our system will actually work; and on the other hand we each of our tests to be an controlled experiment of our implementation, without coupling to shared mutable state.
In some cases, we can disguise some of the coupling by using inert substitutes for our dependency as the default case, so that our implementation classes are isolated unless we specifically opt into a shared configuration.
So for PlaceOrder, it might look like using a default CreditCardService that always answers "yes, the customer has enough money". Of course, that design only allows you to test the "yes" branch in your code - to test a "no" branch, you are necessarily going to need to know how to configure PlaceOrder with a CreditCardService that declines credit.
For more on this idea, see the doctrine of useful objects.
More generally, in TDD we normally take complicated designs that are hard to test and refactor them into a design where something really simple but hard to test collaborates with something that is complicated but easy to test.
But for that to work at all, the components need to be able to talk to each other, and if you are going to simulate that communication in a test you are necessarily going to be coupled to the "implementation detail" that is the protocol between them.
For the case where that protocol is stable, having tests coupled to those details isn't, of itself, a problem in practice. There's coupling, sure, and cost of change, but if the probability of change is negligible then the expected cost of that coupling is effectively nothing.
So the trick is identifying when our tests would require coupling to an unstable implementation protocol, and figuring out how to best mitigate the risk of change.

Where to put *serialization* in SOLID programming

I have business objects, that I would like to (de)serialize from and into a .yaml file.
Because I want the .yaml to be human readable, I need a certain degree of control over the serialize and deserialize methods.
Where should the serialization logic go?
A) If I teach every object, how to de/serialize itself, but that probably violates the single-responsibility-principle.
B) If I put it inside a common serialization module, that might violate the open-closed-principle, since more business objects will be added in the future. Also, changes to objects need now be performed in two places.
What is the SOLID approach to solve this conundrum for tiny-scale applications?
Usually in this kind of situation, you'll want the business objects to handle their own serialization. This doesn't necessarily violate the single responsibility principle, which asserts that each object should have one job and one boss. It just means that the one job includes serializability. The user owns the business object, and wants to be able to serialize it, so the requirement for serializability comes from the same place as those other requirements -- the user.
There are a couple danger areas, though. Firstly, do you really need to insist that the business object is serializable, or can you leave it up to the user to decide whether they are serializable or not? If you are imposing a serializability requirement, then there's a good chance that your are violating the SRP that way, because as you evolve the serialization system, you will be imposing your own requirements on the objects.
Second, you probably want to think long and hard about the interface the these objects use to serialize themselves. Does it have to be yaml? Why? Does it have to be to a file? Try not to impose requirements that are subject to change, because they depend on particular implementation decisions that you're making in the rest of the system. That ends up being a violation of SRP as well, because they those objects have to evolve according to requirements from 2 different sources. It's better if the objects themselves initiate their own serialization, and can choose the implementation to the greatest extent possible.

Use Case: Almost same use case, different actors

I have one use case "Transfer request" linked to two Actors (Analyst and Inspector). The scenario/exceptions/alternate flow are essentially the same, but for each actor the pre-conditions and post-conditions are different. The request has different states, and that final state and previous states differ depending on the actor that's calling the use case.
How can I resolve this issue efficiently?.
It sounds to me like your use case might be too abstract to be useful.
Sure, at a very high level of abstraction the steps might look the same, but if you have a different set of pre/post conditions, different states etc.. then maybe you should be using different use cases.
It could be something like "Transfer payment request" and "Transfer inspection request"
If indeed there is a similarity in steps of the execution then you can still, at the locical level, create an Abstract class "Request" that takes care of the common parts. The subclasses PaymentRequest and InspectionRequest can then take care of the particulars of their own context.
Just remember that use cases are primarily meant to agree on the requirements with the business. So for the sake of clarity it is often better to avoid all too abstract use cases.
You simply formulate those conditions as constraints in the pre-/postcondition. (It's simple like that.)

Precondition in an Use Case

I'm creating a Use Case for a project for school and I'm confused about the precondition field. I can understand preconditions like, "must be logged in" or "needs an account."
But what if the software is a local setup? To me, it sounds logical that a precondition is that "the software needs to be installed." But on the other hand, this precondition looks weird because otherwise, you would not have this Use Case in the first place.
So my question, in short, is: Can system requirements or software installation be a part of a precondition?
Really good question that tackles the heart of the UML problems - it's weak semantics. The answer to your question is therefore - could be right, could be wrong. UML sets the notational rules, and defines only basic element semantics (e.g. "A precondition must be fulfilled in order...").
We could even go further with preconditions like "operating system should be correctly configured" or even worse - "computer has electricity"... These discussions can easily turn philosophical. :)
In my experience there is a way to make use cases efficient - build another UML model, a complementary one, which would be used to formulate preconditions, postconditions and even use case scenarios (the same question you made for preconditions can be made for scenarios as well - which is a correct abstraction for a scenario? Or, is "turn on computer?" a valid step in a scenario?).
In order to achive this I normally use conceptual class diagrams - I model my domain and then express pre/post conditions and scenarios in terms of these elements (classes and their attributes) AND ONLY USING THESE ELEMENTS. This make a lot of sense, especially knowing that pre/postconditions query system's state, neatly reflected by objects/values.
Returning to your example, if you wonder about the precondition "the software needs to be installed", you simply ask yourself "Do I really need a class 'Software' with an attribut 'isInstalled'?"
Then you most likely realize that you probably do not need this precondition because it is simply too "low level" and out of my domain's scope. Now you just need to define your domain. :) Here is a simple example of a similar situation, demonstrating the idea (keep in mind that use case and class models are drawn on separate diagrams):
This method not only make it easier specifying use cases, but also make complementary class model which permit domain specification, business rules identification and a first abstraction of the system design.
Good luck and have fun!
Let's keep in mind that use cases are really high level requirements on what your software/systems needs to implement. In this way, the preconditions should only relate to the software (or system) you are building and not to external elements (e.g., "the computer is connected and turned on" is not a good precondition). As you start to realize your use cases as sequence or activity diagrams, for example, the preconditions give you some clue that a check may need to happen in the software, maybe necessitating a call to some other operation/module/class. This realization exercise will help you determine the form that the precondition will take, and even if it is still required depending on the structure of your application.
To take you example, the "have to be logged in" precondition may be necessary if you expect different behaviour if the user is logged in or not, or if different privileges are required for the operation.
So, some system requirements could be preconditions (e.g., connectivity to a device) and some software installation may be a precondition (e.g., a required companion product integration), but only if they have a direct effect on the use case itself. The installation of the software for your app should not be a precondition: the app can't check if it is not installed as it can't run and it it does execute then it is obviously installed...
For me, system requirements and/or software installation can be a part of a precondition but as always it must be relevant in the context of your use case.
For example, your system will have a different behavior if a hardware or a third tool is or is not installed.

Coupling and how to reduce it

In which of the following lines of code coupling occurs?
What is the kind of coupling? What is the problem induced by this coupling? How can the code be refactored to reduce coupling?
One way to approach this is to look at everything that the function/method depends upon.
It explicitly depends on its arguments - a form of control coupling, in this case.
However if we tried to compile this method in isolation, we can see all the other objects that it depends upon:
Log, and some specific methods of Log (note that we always seem to call both of these methods)
IGNORE_USER_REQUESTS
LOG_VERBOSITY_LEVEL, which is repeated 4 times in this method
A specific Collector class.
mem and 4 attributes (size, startAddress, etc) - probably a form of content coupling
I think you could argue that there are several cases of common coupling (shared global variables) there, though it's not terribly clear from the limited context. And the variables may be scoped within an object or package, not truly 'global'.
Then consider:
What would happen if we wanted to change one of these items. For example, if we wanted to use a different Collector implementation, or a different logger; or the structure of mem changed?
How would we test this method?
Note that we can't properly assess the impact of this coupling without understanding the wider code; if these objects are encapsulated within a single small class, for example, then the impact is less than if these objects are scattered throughout the entire code. Similarly, trying to refactor this code is a bit tricky in isolation, since we can only guess what we might be affecting elsewhere in our hypothetical code, and which objects we are free to refactor (some may be from 3rd party libraries, for example).

Resources