I came across the super interesting blog post about a concept the guy called message obsession. So it's a code smell that the guy claims to have invented, and he puts it in contrast to the code smell called primitive obsession.

So he's saying: if you're pushing primitive obsession too far, you may end up with message obsession. And actually I think the concept makes a lot of sense. Let's talk about what he means with the message obsession.

So here's the example. Let's say that you have a system where you have some unit. By unit I mean soldier or something. I do not mean a code unit by using this word. So you're making some kind of game, and then you want to move the unit around. So you have an instance of a unit, and you want to be able to send messages to the unit, to make it move. To make it move east, or west, or north, or south. So if you're being obsessed with primitives, perhaps what you would do is that you would have a "move" method on the unit. And then when you call the "move" method, in other words when you send the message "move" to the unit, you pass two arguments: an x-coordinate and a y-coordinate.
class Unit {
    move(x: number, y: number) {}
}
So as if you would send 2 as X and 2 as Y you would move horizontally two units and vertically two units. But of course, as you can see this is evidently the primitive obsession because the domain concepts are sort of hidden, the domain concepts are sort of implicit rather than explicit. I mean if you pass a positive integer for X for example, it's not obvious whether that means right or whether that means left. Are positive right and negative left or are positive left and negative right. Somebody could have a different interpretation so this information is implicit rather than explicit in your program.

So, to combat this what you might do is that you might create four methods on the unit "moveWest", "moveEast", "moveNorth" and "moveSouth".
class Unit {
    moveWest(points: number) {}
    moveEast(points: number) {}
    moveNorth(points: number) {}
    moveSouth(points: number) {}
}
And yeah, that might be better because you've made information explicit absolutely, but there are still some problems. Such a change in code encourages you to use conditionals. It encourages you to use conditionals because you have named methods that you in other places will have to call. So let's think about a dynamic dispatch. You can't change a call "moveWest" to "moveEast" at runtime without having to somehow switch over something and then either call "moveWest" or call "moveEast".

A proper solution or a more proper solution to this problem would be to make heavier use of the composition. So, in other words, to have some kind of direction class instantiate a direction object, and then pass that direction object to the unit's walk method. So you will have the walk method which takes a single argument, and what you're supposed to pass is an instance of a "direction" class.
interface IDirection {
    x: number;
    y: number;
}

class Unit {
    move(direction: IDirection) {}
}
So with that, you've eliminated the primitive obsession, and you've increased the composability. And you still have the ability to name things, because you can still save a direction into a variable, and you can give that variable the name "moveWest". And you save another direction into another variable and name that variable "moveEast".
const unit = new Unit();
const northWestDirection = directionBuilder.getDirection(2, 2);
unit.move(northWestDirection);
And through that, you've almost achieved the same explicitness as you had in the example where you had explicit methods name but you also have very high composability.

I see such kind of behavior as "message obsession" from time to time now, so I think that these thoughts might be useful for someone. Here is an original blog post I was talking about in this article Message Obsession