Skip to content

MechanismPolicyMeld

Ben Christel edited this page Jul 11, 2022 · 1 revision

A mechanism/policy meld is a CodeSmell in which the name and FormalParameters of a Routine indicate that it depends on both domain-specific/application-specific concepts ("policy") and infrastructural concepts ("mechanism"). This is a specific kind of low Cohesion.

Example

from https://github.com/testdouble/unusual-spending/tree/f77a06e7223a4426a503a3439d56010eebe47101:

email(userId, subject, body)

Here, userId is an application-level concept, but email, subject, and body are infrastructural, application-agnostic concepts.

Here is the fixed version:

// `from` and `to` are email addresses
email(from, to, subject, body)

Note that this refactoring reveals another smell: the parameters now form a DataClump. Fixing that leads to

sendEmail(new Email({from, to, subject, body}))

This allows the Email type to form the boundary between app-specific and app-agnostic code. See Boundaries.

Why it hurts

Interfaces that mix mechanism and policy concepts are reusable in fewer situations. They can therefore lead to subtle forms of duplication as a codebase grows, where many pieces of code end up having to know about the same mechanisms. In the email example, we might need to send emails to AdminAccounts and lists of users (cc'd or bcc'd?) as well, and the code for doing those things would duplicate some of the code in email.

The smell can make it harder to debug problems. For example, suppose users don't receive an email when they're expecting one. There are at least two places where the bug could be: email, or its caller (the code that determines the userId to send a email to). We have to investigate both of these possibilities to narrow down the cause of the bug. In contrast, the data passed across the sendEmail interface unambiguously describe the mail to be sent, so if sendEmail works once for a given email address, it should work always. And because sendEmail is app-agnostic, it is more likely to be Stable and BattleTested—its many usages make it more likely that most of the bugs have been reported and fixed. Both of these characteristics of sendEmail mean the problem is much more likely to reside in the code that constructs the arguments to sendEmail—that is, the app-specific code.

When we UnitTest our app-specific logic, we can mock the sendEmail interface (or simply assert on the Email returned from some Function, if we fixed the DataClump smell) and thoroughly test the interesting parts of our code. The same is not true of the email interface. If we mock it, we cannot test that the userId is translated into an email address in a way that meets our needs.

Other problems can arise, too. In the email example above, the smelly code presumably fetches a user record from the database to get its email address. That can lead to all sorts of problems, including performance (if we already have a user record in memory somewhere, we can't use it; email will always fetch it from the database) and data consistency (what if the user just changed their email address, and we have another copy of the user record in memory that has a different email address?). If we have an open database transaction in which we're updating the user record—too bad, the email Routine will load the user record outside the transaction, and there's nothing we can do about it.

Note that the email interface does not just make a different choice about database access, compared to sendEmail. It strictly limits our choices about when and how data should be accessed. With sendEmail, we could reload the user, and even reload it outside of the ongoing transaction, if we chose. With email, we are barred from making that choice.

Clone this wiki locally