| There are abstractions that are not domain related, those exist as well, and I'd consider them more a part of product design, but software design can benefit from good abstractions at multiple levels and constantly do, but knowing how to use good abstractions and design them is very hard, bad ones or bad use of them will be worse than none. For example, a schema is an abstraction. Choosing to have a strictly defined schema for your stored data is choosing to add a layer of abstraction. You could say simply store things as JSON, directly, serialize whatever object you have into JSON and be done with it. Or you could choose to add a layer of validation and create a JSON Schema. Then set things up so the concrete data is created using the abstract schema definition in a way that also automatically set ups validation of the data using that schema. Now sometimes this is overboard and too complicated for whatever you're doing, sometimes this is an amazing addition to a code base that really simplifies and make you more productive. Edit: I'll give another more simple example as well, to show how abstractions are relevant at all levels. Take a Player Class, where Player has a position in the world map. You could go the concrete direct route: Player {
String name;
List<item> inventory;
int health;
int x; // x position in world
int y; // y position in world
}
Or abstract out Position: Player {
String name;
List<item> inventory;
int health;
Position position;
}
Position {
int x;
int y;
}
This is more indirect and there's an extra abstraction, Position, but there are scenarios where it's much better like that, mostly if positions are often managed by other things or moved around and manipulated in similar ways be it for Players or Npcs or Cars, etc.And there's scenarios where it wouldn't benefit much. The solution where position is just concrete ints on the existing Player class is more concrete and direct, but not always the best. Now a mistake I find mid-level engineers make is they'll read a blog or book saying it's much better to abstract out Position like this for x, y, z reasons. And then they'll do it for everything, they'll apply it to `name` for example: Player {
PlayerName name;
...
}
PlayerName {
String name;
}
Doing that will make your code base a nightmare, future you and other engineers might hate it, why is everything abstracted like this? What's the point? What's the reasons behind it? What's the benefits?You could conclude never to abstract anything ever again, and that would be better than the monstrous over-abstracted everything for no reason and often badly implemented at that, and that's an improvement, later on you'll get even better and learn the nuances, when, why and what abstractions in just the right place, the right amount, in just the right way, and it'll be even better. |
The focus on building abstractions is misguided. You don't build an abstraction because you have stuff lying around that implements things -- you build an abstraction because you need it to do your job. That's the only valid reason to ever build an abstraction: you, as the consumer, need the abstraction to do (or to define) your own job. As a consequence of this, most abstractions should be defined before they're implemented. It really feels like most people miss the point on this one, and that's why we end up with bloated abstractions. They're not about what you have. They're about what you need.
That means you should actually have lots of abstractions (assuming you have lots of different needs throughout your code), and they should all be simple, small, and clear. It should be obvious how to implement them, and obvious what they're used for. They have to be: that's how they were built to begin with.
(In fact, while we're at it, the focus on classes is misguided too. Why does everyone think you need to make classes that mirror common nouns in real life? Bad CS education?)
I could absolutely see "Position" (and, critically, everything in it) as something some service needs to do its job. In fact by simply looking at that class, I've learned a lot about how your game works: it's 2D (no Z) and probably tile-based (ints, not floats). We've made a decision: that's how position works in this game. How does movement work? Start that next -- it will use Position. Keep picking away at the edges, making useful decisions about the game, etc. Build abstractions only when you need them to answer that question: "how does X work in this game?" You will never get to the point where you build a "Player" class like that, which is why I can confidently say that a codebase with such a class must inevitably suck.