4

The problem

A famous quote says: "There are only two hard things in Computer Science: cache invalidation and naming things"

We all know how important ubiquitous language / good names in DDD are.

I often find myself stuck thinking extremely hard about a good name for a new domain term. With experience I acquired instinct to try my best and come up with ideal name from the very first line of code, which is not always possible unfortunately.

But why? Why don't I just give it a good-enough name and rename it later? The reason is that domain terms tend to metastasize to all corners of code base while existing tools for renaming are shockingly primitive.

For the sake of example let's assume I want to rename term "InvoiceEntry" to more generic "DocumentEntry" (it can be anything else) because I found out that related code is reusable for different types of documents not just invoices.

Clarification: I explicitly use wording "domain term" rather than "domain entity" or other because there are other entities, classes, methods etc. (in a nutshell - identifers) that are derived from the same term. I don't even talk here about possible typos, let alone related database schema, string resources for UI etc.

If you are lucky, upon renaming of a type a smart IDE will suggest to rename some instances of the type, derived classes/implementations (if any), and will search for term in comments and dynamic invocations in absolutely context-insensitive manner i.e. just ambiguous suggestions not sure things. And that's it.

But the job is not over. At this stage I also must try "Find symbol" and then few iterations of good old "Find in Files" (including search with fuzzy free text chunks of the term that could be related e.g. "invoice entry", "invoice", "entry" etc.) which can yield hundreds of potentially related occurrences. All I can rely on is my attention, diligence and good judgement to make sure nothing related is missed and nothing irrelevant is affected. IDE simply can't resolve this ambiguity.

Conclusion: even if with best available IDE it is surprisingly laborious task to make a clean rename of domain term in a mature project. Doing it is a torture, reviewing someone else's renames is even worse.

It is easy to do the sloppy rename with a good IDE. However sloppy rename is arguably worse than no rename at all, otherwise your code can quickly turn into a mess (see "Broken windows theory")

Question and ideas of solution

If naming is so important then why renaming tools are still so poor? Can we do better than this?

Disclaimer: I will talk about strongly-typed languages as I don't use dynamic languages too much.

It's worth mentioning strongly typed functional languages with good type inference, they are a bit better at this however still far from ideal (and I can't just switch an old project to a functional language anyway):

  • Haskell has a tradition of giving abstract names to identifiers such as a, b, c, f etc. This is unthinkable in mainstream OO languages but not necessarily a bad thing. After all, if there is nothing else to tell about identifier that this is the instance of the type then it might be OK to name them "a" and "b" or "this" and "other" or alike. Abstract identifiers 100% immune to renames ("InvoiceEntry a" renamed to "DocumentEntry a" still makes sense, "DocumentEntry invoiceEntry1" doesn't)

  • with strong type system and type inference it is encouraged to create generic functions instead of specific types. More generic functions equals fewer mentions of specific domain term equals less code affected by renaming of a domain term.

Strongly-typed domain term metadata (an idea)

What I'm looking for is some kind unambiguous metadata mechanism to describe relations between identifiers / code elements and domain terms in ubiquitous language - and be able to traverse those relations to do something useful, specifically fast renaming of a domain term and everything that is derived from it.

I have an idea how I would do this in C#. Here's pseudocode (by no means valid or clean, but should make a concept clear):

// domain terms
public static class DomainBloodlineTerms
{
   public static const string InvoiceEntry = "InvoiceEntry";
   public static const string Repository = "Repository";
   public static const string Id = "Id";
...

using _ = Domain.Static.DomainBloodlineTerms;
...

// a custom attribute to explicitly define relations 
// between the identifier (class name in this case) and domain terms
[DomainBloodline(_.InvoiceEntry, _.Repository)
public class InvoiceEntryRepository
{
...
    //  a method with the bloodline
    [DomainBloodline("Get", _.InvoiceEntry, "By", _.Id)]
    public GetInvoiceEntryById(
        // a parameter with the bloodline
        [DomainBloodline(_.Id)]  int id)
    {
         // a local variable with the bloodline 
         // (probably an overkill, but imperative style is possible too)
         DomainBloodline.Verify(_.InvoiceEntry, "Dto")
         var invoiceEntryDto = ...

I then can implement a couple of custom Code Analysis rules

  1. enforce that every identifier in namespace (or in project/solution) is annotated with the Bloodline attribute.
  2. enforce that all annotated identifiers are in sync with its "Bloodline" attribute

and custom refactoring:

  • if member of DomainBloodlineTerms gets renamed then related identifiers will be renamed too.

with both rules above I can rest assured that everything related gets renamed. e.g. DomainBloodlineTerms.InvoiceEntry renamed to DocumentEntry - and related class, method, parameter and even variable will be renamed too.

Other benefits:

  • disambiguation of identical terms from different sub-domains
  • protection against typos

Other suggestions?

I don't like the "bloodline attribute" idea for few reasons:

  1. it is tedious and verbose
  2. "Find symbol" can already facilitate some parts of it (except checking for typos)
  3. it doesn't help to pinpoint related artifacts outside the code (SQL schema scripts, string resources etc.)
  4. I have a hunch that I'm reinventing the wheel

Do you know any existing solutions or better ideas to achieve the same result? .NET or any other language/ecosystem is good as a source of inspiration.

  • This is the third time I've seen essentially the same question over two different sites in the last day or two. I'm genuinely confused: how is this not solved cleanly and definitively by simply doing a global find and replace? – Robert Harvey Mar 28 at 15:20
  • @RobertHarvey. At very least there is always risk of ambiguity, typos, shortenings, abbreviations etc. and it increases with the size of solution. Could you also point to the duplicate questions? I browsed before asking but didnt find any. The fact they are asked clearly indicates that global Find and Replace and typical IDE refactirings are not context-sensitive enough to make a truly clean and effortless rename. – KolA Mar 28 at 18:11
  • In my example "InvoiceEntry" can be referred to as just "invoice" or "entry" in parameters, local scope vars or method names just for the sake of brevity (I'm not saying it's good but again I don't know existing tools that would prevent it from happening). Find and Replace of "entry" is likely to hit hundreds of unrelated occurrences, which should be reviewed manually. – KolA Mar 28 at 18:21
  • If you're getting into that level of detail then you have larger problems. What about your data stores? Do you have to rename the tables and columns in those as well? – Robert Harvey Mar 28 at 18:24
  • Scope of the question is just code that has no clients outside the project (i.e. not published), that would be a good start. Data store can fall into this category however it will need some manual migration script anyway – KolA Mar 28 at 18:33
4

In the absence of any better out-of-the-box tools just keep using global "Find and Replace" and standard IDE refactorings plus practice some discipline to minimize risk of false positives and misses.

  • Avoid ambiguous and conflicting domain terms like a plague, including terms from standard framework and libraries.
  • Always refer to domain term in its original name, avoid abbreviations, shortenings etc.
  • Don't overuse the term if you don't have to. Following method signatures are equally clear and type-safe but one is more immune to renames than the other
InvoiceEntry LoadInvoiceEntry(int invoiceEntryId)
...
InvoiceEntry Load(int id)
...
void SaveInvoiceEntry(InvoiceEntry invoiceEntry)
...
void Save(InvoiceEntry x)
  • "InvoiceEntry x" in example above is a controversial one, however if there's nothing else to say about the object except that this is some object of InvoiceEntry type then why not give it an abstract name? It's still OK to name it as "InvoiceEntry invoiceEntry" as long as it is an original term and it's not shortened to "entry", "invoice", "e", "i" or alike.
  • Where possible, avoid compound terms e.g. Invoice is better than InvoiceEntry as latter has higher chance to be shortened to "invoice" or typed as two separate words in comments / string resources etc.
  • Make sure these guidelines are followed by other contributors during code review.
  • Consider to automate some of above in the form of custom code analysis rule.
  • Last, but not least - always try your best to come up with ideal name from first line of code. The wider scope of the domain term the harder you should think.

Regardless of how stringent and diligent you are, there always will be concerns to address and review manually, for example:

  • Versioning, if your code consumed by third parties / other projects.
  • Making sure renamed domain term still manifests itself nicely in user interface e.g. text labels not clipped, controls still aligned nicely etc.
  • Data store migration (and feasibility of that)
  • Config values, deployment scripts
  • Documentation

"Extreme" renames will always be labor-intensive, whether it is transition of the project from code name to a market name or rename of a city (seriously, such things still happen in our days e.g. recent rename of Kazakhstan capital Astana).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.