So I wrote a bit last week about the importance of not lying through your APIs, but I didn’t offer any examples on how to improve the examples I gave.
Let’s take one at a time and consider how they might get improved.
The first case we had was the mis-named API.
You can fix this simply by having the method name describe what the API does.
This may lead you to refactor the code a bit, once you understand better what it does.
The next case was an API with fine print.
When addressing these, it is often a good choice to refactor things such that you end up with simple APIs that do straightforward work, and then other APIs that consume them with more nuances. Note that I’ve removed the communication through environment variables and instead I return an explicit out parameter. This changes behavior a bit, but you can have the caller of this API set it if you still need it.
// This won’t handle any special cases.
Of course, if you can get rid of the fine print altogether that’s usually the best choice.
Next, we have the case of the API that does a lot more than you asked for.
The first step is usually to break this up into meaningful, lower-level bits of functionality you can then combine.
public static void CompressFile(string path)
// Only compresses the file.
public static void RemoveUnusedTempFiles(string path)
// Deletes files that are associated with the given one and no longer
Next, it’s possible that you just had a naming problem. In this case, this operation looks a lot like what would happen when a user presses the Save button in an application that may have auto-save and other similar features. If this is the exact mapping we need and our policies are unlikely to change, it might be a simple matter of having a renamed entry point.
Another option is that the operation was doing many things because it really was a more complex beast. If so, sometimes you may choose to make the operation a class of its own, where you can configure it in different ways to control the more subtle aspects, and have a richer description of what should happen and possibly what the results were.
It also has the nice benefit of allowing for cleaner ways to extend it with more functionality and with things like progress reports and callbacks.
Finally, we have the teasing API example. In this case, we have an API that promises more than it will deliver.
The most straightforward way to stop lying here is to implement the missing functionality. Maybe you didn’t have time the first time around to handle special customers, but now you’ve found the time and you can round out your API to remove special cases.
If you still don’t have the time or it’s just not feasible, you can sometimes constrain the arguments the method will take to be more specific.
Interestingly, the exact example I showed before won’t work – we wanted to avoid a special case and handle a more general one; instead the problem with over-promising is that the API seems to handle a general case but only handles a special one.
The problem with this API actually needs to be solved elsewhere: why can’t SpecialCustomer behave like a Customer? Assuming SpecialCustomer inherits from Customer, it would be violating the Liskov substitution principle, so looking at the classes under this lens and trying to fix this would probably be a better approach.
Another problem with this particular API is that the functionality is outside the class, so it’s not clear how it will handle more-derived types if there is a meaningful way in which that should work: will properties from SpecialCustomer be written out or not? A much better approach would be to have this method exist on the Customer class, and then SpecialCustomer could specialize that as needed.
As a last resort for most lying APIs, you can usually change the names to be more descriptive of whatever you would have lied about. This is typically a last-recourse solution when there are other options, because it’s usually leaving the underlying problem unaddressed, and it leaves the user wondering whether there is another API it should go to that works better, or whether this is some optimized special case, or why they would see such an API in the first place. Confusing someone is better than lying to them, but not by much.