DEV Community

Discussion on: In Defense of Clean Code: 100+ pieces of timeless advice from Uncle Bob

 
mindplay profile image
Rasmus Schultz

Both of those ideas aren't criticisms of abstraction though, those are criticisms of doing abstraction badly.

I don't agree. The criticism is that of abstracting the wrong things. The third-party library you're using is already an abstraction of something - and there could be reasons to further abstract that, but often there isn't, and just avoiding coupling, in my opinion is definitely not the right motivation.

To solve the first problem, just... don't add the third-party library's implementation details into your abstractions's interface. Leaking the implementation details defeats the whole purpose of abstraction.

Even if you don't leak implementation details, you're going to "leak concepts" - ideas from the underlying library are likely going to bleed into your domain, even if things like types and interfaces do not.

Some reasons I might choose to abstract would be:

  1. The library is really complex and does a lot more than I need - in that case, I can avoid direct coupling to a complex library by hiding it behind a simple interface.

  2. The library doesn't quite do everything I need - in that case, I can build an abstraction that adds in the missing details, and again avoid direct coupling to something that wasn't quite what I needed in the first place.

On the other hand, why would I abstract something if it's already (more or less) exactly what the project needs? If I put my own very similar units in front of some library units, any ideas of being decoupled is really just an idea - if anything changes, it's practically guaranteed to break my abstraction.

I think that's the case he's talking about in the video.

Every line of code, whether that's your code or library code, adds complexity: every line of code is a liability, so every line of code needs to have a specific, measured reason to exist.

In my experience, the most successful projects are always the ones with less complexity.

So it has to be a conscious, case-by-case decision, in my opinion.

Thread Thread
 
peerreynders profile image
peerreynders

If I put my own very similar units in front of some library units, any ideas of being decoupled is really just an idea.

This is assuming a one-to-one distinct abstraction to concrete library relationship. That type of alignment isn't necessarily the best way to move forward.

In my experience, the most successful projects are always the ones with less complexity.

I think it's more important to evaluate if complexity is managed appropriately. In my view OOD invariably adds complexity in order to manage complexity — it can work but it often isn't a slam dunk.

So it has to be a conscious, case-by-case decision, in my opinion.

That's pretty much a given. Guidelines tend to be a starting point, not some absolute truth.

What ultimately devalued the video for me was the example - why would there be a need for a concrete messaging abstraction? The actual goal is to have the application logic be "ignorant" of the messaging solution that is being used to handle messages. This idea is similar to Persistence Ignorance (PI):

Well, PI means clean, ordinary classes where you focus on the business problem at hand without adding stuff for infrastructure-related reasons.
Jimmy Nilsson, Applying Domain-Driven Design and Patterns 2006, p. 183

"The Application" will only need to send a finite number of message types, and receive a finite number of message types. Worst case each send-type has its own function into the infrastructure and the application exposes a separate function for each receive-type. The application is only interested in providing the data for outgoing messages and extracting the data from the incoming messages. The application really doesn't care what happens on the other side of the application boundary.

Quote

… and then you find out that there are messaging libraries that abstract the underlying transport whether it be Azure Service Bus or Rabbit MQ, etc. …

Rabbit MQ has no business being inside the application boundary.

To use J. B. Rainsberger's terminology:

  • Rabbit MQ has to live in the Horrible Outside World (HOW)
  • The application exists in the Happy Zone (HZ).
  • The HOW and HZ are separated by the DeMilitarized Zone (DMZ; where the "adapters" live; narrowing API, pattern of usage API)
  • The HZ can depend on the HZ
  • The DMZ can depend on the DMZ
  • The DMZ can depend on the HZ
  • The HOW is permitted to depend on the HZ
  • The DMZ may depend on the HOW
  • The HZ cannot depend on the DMZ
  • The DMZ is allowed to talk to the HZ by implementing an HZ interface.
  • The HZ cannot depend on the HOW (… and yet this is exactly what many frameworks encourage you to do — I call it the creepy hug problem — it's nice to be hugged unless the hug is a little bit too long and a little bit too tight by somebody you don't know that well)
  • The HOW is allowed to talk to the HZ by implementing an HZ interface.

FYI: Mock Roles, not Objects

Quote

Now again the reason you're abstracting these things is because you want to isolate them. And you might want to isolate them because you don't want to depend on them directly. Again most people say so I can swap it out.

The "swap out" argument is specious but isolation is the prize.

Isolation is the pre-requisite to being able to leverage fast micro-tests — "tests to detect change", i.e. establish rapid feedback as we refactor the code to reduce the volatility in the marginal cost of adding the next feature.

Doesn't this HOW/DMZ/HZ stuff slow us down?

In the beginning perhaps but again J.B. Rainsberger explains The Scam:

The cost of the first few features is actually quite a bit higher than it is doing it the "not so careful way" … eventually you reach the point where the cost of getting out of the blocks quickly and not worrying about the design is about the same as the cost of being careful from the beginning … and after this being careful is nothing but profit.

He acknowledges that "the scam" is initially incredibly seductive but eventually there comes the point where the cost of continuing is higher than the cost of starting again.

So the initial investment is aimed at going well for long enough, so you'll beat fast all the time.