Code Health: Make Interfaces Hard to Misuse (2018)
testing.googleblog.com
testing.googleblog.com
If you require function `setUp : unit -> unit` to have been called before function `doThing : unit -> unit`, you can instead alter the types, introducing a `type Token = private | Token` which the consumer can't create for themselves but which your implementation can create. Then just tweak the types so you have `setUp : unit -> Token` and `doThing : Token -> unit`; since the user can't make a `Token` themselves, they can only have obtained it from calling `setUp` first.
A few things to keep in mind when doing this:
- If we end up with a classes which only has a single method (e.g. 'DisconnectedDatabase' with only a 'connect' method), then it might be better as a standalone function, or a smart constructor, etc. ( http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom... )
- We can use this approach to prevent code being called prematurely (e.g. querying has to come after connecting), but we can't prevent code being called too late; e.g. trying to query after a 'Database::disconnect' method has been called. (This is where linear/uniqueness types, etc. can be used)
You can get pretty close in C++. All it takes is for the disconnect method to be rvalue-qualified:
void disconnect() &&;
and to turn on use-after-move compiler warnings. Now disconnecting has to be invoked as:
std::move(db).disconnect();
and if you refer to db after that Clang will tell you to knock it off. (Of course, for a database object, it probably makes more sense to use RAII and disconnect in the destructor, but this pattern can help if you have multiple ways for the object's useful lifetime to end.)
That won't catch users who have a pointer or reference alias to the object, but that's more like a use-after-free risk, which exists any time you have references.
However, the general mindset is one I use all the time. For example, if I've written a state machine that is represented explicitly in the types, I might be able to encode some of the state transitions in the type system directly, so the compiler prevents me writing certain invalid transitions. For example, if state B can only be reached after state A, I might write the in-memory representation of B to contain an in-memory representation of A; this guarantees I must have passed something through state A to get anything in state B. Doesn't save you from everything - in particular it doesn't solve your "passing the token to the wrong instance of the object" problem - but any constraint on the state space is useful in my book. When the state machine gets sufficiently large, in my experience, the benefits of the restricted state space start outweighing the negatives of manual token shuffling.
(I have a blog post brewing about this general class of techniques, but I don't know when it'll be ready.)
I sadly don't know enough F# to even begin to think of something to make the tokens context safe.
How Do I Make This Hard to Misuse? https://ozlabs.org/~rusty/index.cgi/tech/2008-03-30.html
What If I Don't Actually Like My Users? https://ozlabs.org/~rusty/index.cgi/tech/2008-04-01.html
Bonus points for avoiding mutable state in classes entirely. It’s certainly not always possible, and requires a shift in mindset, but we’ve been trying to actively avoid state not set by the constructor and it’s had a very positive affect on our codes readability and reusability.
Maybe with named method parameters, it's better, but Java does not have those.
Of course, lots of parameters are a code smell, I know. But what do I do? We have a business logic entity that always needs a creator (a user), a current owner (another user), the user who last modified it, a creation timestamp, a last modification timestamp, and a couple of other things. (And that's before we get to the actual data that's stored in the entity.)
https://en.wikipedia.org/wiki/Sequential_coupling
Having methods called "Initialize", in addition to a constructor, as a good indicator of sequential coupling.
Foo
Foo.Id
Now what is an ID? Just a long. But now when I call a function like getFoo(Foo.Id id) it is statically obvious what is going on.
Its little things like this that make it obvious what should happen vs documented and then yelling at you for not remembering the exact interface.