The Class1-Class2 Naming Antipattern for Associations

Join classes (aka join tables) are entities whose main purpose is to associate one object (aka record) with another in a NxN relationship.

A common and decades-old pattern, which is almost always wrong, is to name these classes after both association classes.

Examples of NxN relationships:

  • PersonStock maps owners to their stock.
  • UserFeed maps users to feeds they are subscribed to.
  • StudentCourse maps students to their courses.

What’s wrong with these names? First, they are awkward to say and cumbersome to deal with in code (as a general rule, multi-word entities are best avoided, because it becomes confusing and ambiguous when they are combined with other words). Second, they are redundant to anyone who is looking at the class’s foreign keys (admittedly, some redundancy is okay if it makes the code more understandable, but one should always be weary of a naming scheme which could be auto-generated by a trivial script). Third, and the biggest complaint: it’s wholly unnatural to anyone versed in the domain, therefore not a good model of reality. Only programmers and DBA use terminology like this; domain specialists do not.

The fundamental problem is it frames these associations as being entirely about the things they associate, instead of treating the association as a first-class citizen, which is inevitably how they are treated by a practitioner in the field you’re modelling. Once you start seeing the association as a model in its own right, you can start to enrich it with meaningful properties and behaviours. And this is typically true in the real world – associations are more than just dumb pairings of item A and item B.

More than just modelling these associations and finding an appropriate name, it can also prompt you to talk with domain specialists about what actually are the NxN join concepts in this domain.

Revisiting these examples:

  • PersonStock is better modelled as Ownership. Now that we have a concept of “ownership”, we can think about things like when was it created (ownership.created_at) and what kinds of conditions must be required to create an “ownership”. You could do this kind of reasoning with a “OwnerStock” thingy, but it’s more mental gymnastics and takes you a step away from domain specialists.

  • UserFeed is better modelled as Subscription. Now we can attach properties of the subscription, e.g. a ranking/rating indicating how much the user loves any particular feed. This data may then be used to determine how the user is notified of updates and perhaps how the “river of news” is sorted. Or maybe a visibility attribute indicating who can see the subscription, ie is it public that a given user is subscribed to a given feed.

  • StudentCourse is better modelled as Enrolment. Now we can record a “passed” or “grade” attribute against the enrolment and consider pre-conditions for creating an Enrolment, such as looking at the user’s past Enrolments.


p>Not all associations have a natural word to describe them, but even when they don’t, it’s worth thinking really hard about coming up with a new term. The Class1-Class2 name is almost always the road to pain.

Guard Clause Considered Helpful

Apparently, PragDave recently questioned the conventional wisdom about GOTO considered harmful (does this mean all “X considered harmful” articles will be retrospectively struck off the record?). Ivan Moore’s given an example as to why the rule of “a single exit point” sucks, and I agree.

Another reason for multiple exit points is guard clauses. Code units, like classes or functions, ought to look right at a high level. Just like with a piece of writing, it’s important that the overall structure gives you the big picture. Guard clauses help here. You can say, “Okay, let’s get the cruft out of the way – the trivial cases, the exception cases – and now we can focus on the main thing the function’s meant to do.”

  1. void foo() {
  2.   if (nothingToDo) { return; }
  3.   // Implement typical behaviour of foo()
  4. }

is much more readable than:

  1. void foo() {
  2.   if (!nothingToDo()) { // Hold my breath until the end
  3.     // *ugh* The typical behaviour buried in an if-clause
  4.     // And adding insult, we're now indenting more than we need to
  5. }

Steve Freeman points out, Ivan’s particular example could be refactored nicely into a single ternary operator statement. While they don’t scale to larger, more complex, functions, ternary operators are a very handy tool for the reason he explains.