Be generally specific

I was working with some code when my spidey senses started tingling. The code looked generic, but the code the callers was using made it look like there was a problem.

analytics.logEvent(‘product_viewed’, {
  productId: ‘1234;’,
  date: new Date(),
});

public async logEvent(eventName: string, properties: any);

This method was forcing calling code to follow a pattern, which was to be repeated again and again throughout the application. In one sense it feels no different to a utility logging class, but it was trying to be too general, and the general nature of the code was affecting my ability to read and maintain it. My immediate thought was to use specific methods instead of general ones.

logProductPurchase(productId)

This has its own set of problems, one of which is, that there will be a proliferation of methods in the class. But I prefer this.

But what is the right way?

We all like general utility code and reusable libraries, we all move toward it naturally. If I just store everything as key value pairs then I would have a database that could store everything! We lose clarity and speed though, so we always have to be balanced. My problem here is that this general library was making too many demands on the caller. It demanded the caller manage the identifiers and remember the property names. In some weird reverse “Tell, Don’t Ask” principle it required the caller to satisfy its own needs.

The direction we look at a bit of code from can often affect our perception of it. It is reasonable for a buy now page to logProductPurchase but from the analytics service it feels a little out of place. There is a natural direction from specific to general. We look at code from a top down perspective - so we should see things from the caller not the called.

The log event requires a lot of knowledge about its internal workings, and internal data to be public. The event name, the properties are all really owned by the analytics object, which should own its own data and implementation.

It feels right then that the analytics class should be refactored. To have a set of specific methods created, and for the event names and properties to be encapsulated behind this new API.

Conclusion

What interested me about this code was it could be seen as a utility class but it feels like more than that. It’s at a point where it is moving between two states, it goes from being used to using. We have these grey area decisions to make, but used to using, feels like a reasonable marker to put down for making decisions.

The principle, tell don’t ask, and the law of demeter can help to guide you, but they do not immediately ring true here; what I found was: that demanding too much from your callers is also problematic.

We all get into these situations when refactoring. If we are working on a logging object, we see it from logs perspective and not from the callers. This makes us think more generally. If the calls to log were from an application specific class, then perception changes and we make more specific implementations. Either way, demanding that callers manage internal state has negative consequences, which get worse as it is used more and more. Tell don’t ask, and don’t demand.