The API we presented last time had a problem - it required users to remember to call Initialize()
before an instance could be used without problems occurring.
Sometimes this is called temporal coupling
, because the ordering, or timing, of calls need to be just right.
In C#, our object may have a declaration like this:
public class ImportOperation
{
public ImportOperation() {
// ... elided ...
}
public void Initialize(ServiceClient client)
{
// ... elided ...
}
}
and consumption like this:
var operation = new ImportOperation();
operation.Initialize(client);
or in Go, the declaration:
type ImportOperation struct {
// ... elided ...
}
func (op *ImportOperation) Initialize(client ServiceClient) {
// ... elided ...
}
and consumption:
var operation ImportOperation
operation.Initialize(client)
The first, and perhaps most obvious, way to improve things would be to use a constructor, so that an uninitialized instance can’t be obtained in the first place.
In C#, you’d end up with this declaration:
public class ImportOperation
{
public ImportOperation(ServiceClient client) {
// ... elided ...
}
}
and this consumption:
var operation = new ImportOperation(client);
This isn’t a bad approach, but it doesn’t always work.
For one, C# does not have asynchronous constructors, making it impossible to roll Initialize()
into the constructor if it needs await when invoked.
Also, Go doesn’t have constructors - and the idiomatic use of uninitialized structs (with useful zero values) would seem to pose a blocker too.
How else can we make the API one with a pit of success where users can’t get things wrong?
Comments
blog comments powered by Disqus