Communication between layers/modules/subsystems works best when API is simple and self explanatory. Pieter Hintjens wrote a great article about API design.
For today’s article we will focus on point 9: Keep it Simple to Use
Introduction
This post is an extension of following post https://localwire.pl/throw-exception-at-me/. I found worth sharing my thoughts on this subject too :)
Sample Scenario
IList<User> = container.GetUsers("S*")
Developer A wrote “GetUsers(string pattern)” method in UsersModule. Developer B uses it.
Question: When B will ask A “Zero means ‘no records’ or also ‘error connecting to database?’” ?
This is simple scenario where API is not well defined. It introduces uncertainty and questioning. It will make maintenance harder.
Solution 1
Developer A: I will throw exception if something bad occurs. Developer B: So I have to handle your DB issues ? In UI!? Developer C: I will be forced to do “great refactoring” in my module since it depend “heavily” on your methods…
Solution 2
Developer A: Let’s return a boolean status. GetUsers method will return a tuple. A list and “tiny” boolean flag. Developer B: Great. I just check flag and know what to do. Developer C: It’s polluting the API. It’s like extra wrapper in boolean flag…
Alternative solution
How about considering every public method return object in UsersModule as “OperationStatus” ? We explicitly say that all methods are operations therefore they have status.
OperationStatus<IList
All developers have clear understanding what UsersModule can return. It’s an OperationResult<T>
by definition.
This pattern was occurring so often that I decided to add OperationResult<T>
to HandlyLib. Here is the code:
[gist https://gist.github.com/pawelklimczyk/958f74a96e359bdb721b53065b3cd0ae /]
When to use it
I would recommend using it in communication between layers. It has some overhead. Data has to be packed in extra “status” frame. I doubt it’s worth doing that in in-module data flow. On the other hand it is worth investing time and effort to introduce it module-to-module communication, because it give us one and single way of handling statuses.
Go even deeper
You can go even deeper and introduce Convention Tests (still looking for source of this term - mail me if you know something). It’s fairly easy to do so with CodeWatch.
We tell CodeWatch to ensure that all public methods in UsersModule return OperationResult<T>
[gist https://gist.github.com/pawelklimczyk/cff77aec37c36f1a5650d18a94b855f0 /]
This way we can keep our code-base is more structured and better-defines form.