Say what, not how

Distillation through delegation.

Encapsulations and Refactoring

What does “encapsulation” mean? A workable definition could be:

💡 To put what's on the outside on the inside.

What does “refactoring” mean? A workable definition could be:

💡 Restructuring software to improve it, usually without changing its outward functionality.

Developers are familiar with a kind of “general refactoring” where we try to break up chunky functions or classes into smaller parts.

It’s this kind of refactoring we’ll explore. To think a bit deeper about why we do this kind of refactoring so that we can do it wittingly.

The Three Spirits of Encapsulation

There are two primary ways to do encapsulation in most modern languages:

  • Functions
  • Classes

We won’t get into their differences right now.

Currently we’re interested in an important way in which they are similar:

💡 They both let you put stuff that's outside, on the inside.

And both have three primary aspects:

  • An overall responsibility or purpose
  • A strategy used to fulfill that purpose
  • A specific implementation of that strategy

That a function or class has a purpose is straight-forward enough!

But that it has both a strategy and an implementation of that strategy, as separate things; that’s more interesting.

Strategy and Implementation

What makes a strategy and an implementation of that strategy different things?

Here’s a way to think about it:

💡 For any strategy, there are multiple implementations of that strategy

So, they’re not necessarily the same thing.

For trivial cases, they can be — but trivial cases don’t cause us trouble.

Understanding Strategy

Consider this:

💡 No implementation of the wrong strategy is helpful.

You must first have a strategy which accomplishes the right goal, that fulfills a component’s responsibility.

If we have the wrong strategy, the correctness of its implementation is a red herring.

Understanding Implementation

Consider this:

💡 To judge the strategy employed by some code, you must first understand its implementation.

Given some encapsulation, knowing its responsibility, we routinely need to determine:

  • Are we taking the right strategy to fulfill the component responsibility?
  • Does the implementation correctly carry out the strategy?

Yet, we can only know which strategy is even employed by first working to understand the implementation. And we can only know if the implementation is correctly carrying out the strategy, once we know what strategy its trying to take.

The implementation, the code as written, is by far the largest barrier between us and an understanding of our software.

Expressivity

Expressivity is one of those terms that gets thrown around a lot and everyone has their own definition.

But let’s just consider a very particular form of expressivity:

💡 How readily some code says what its strategy is.

Writing Expressive Code

How can we address the tension between strategy and implementation?

How can we write code that says what its strategy is, without the implementation getting in our way?

Distillation

If the strategy is “what is done” and implementation is “how it is done”, then distillation is:

💡 Reducing the amount of detail regarding the how of what is done.

Luckily, our programs are not limited to a single component.

Delegation

We can achieve the distillation we’re after through delegation:

💡 Using some subordinate encapsulation to do something

By throwing those implementation details into some secondary encapsulation we can then delegate to it.

By delegating to the encapsulation we replace the implementation details with a call to that delegate.

Assuming we have good naming convention and style, what is left over is an artifact saying what we’re doing but not how.

The “how” has been hidden inside the delegate encapsulation until needed.

Squinting

How do we know which bits of implementation to group together and delegate out into an encapsulation?

Well, in general, we want to capture the very highest-level description of the local strategy.

However, you can take this too far…

If you’re trying to tell someone how to draw an owl, giving them the single step of “1. draw the owl” isn’t very helpful. It’s too coarse of a description.

owl

At the opposite end of the spectrum we have the implementation itself; all the atomic steps needed to carry out the strategy.

What we want is something that approaches the former without reaching it. We want to express the minimal number of steps but enough to actually differentiate this strategy from the alternatives.

Unfortunately I know of no generalized method for deriving what level of description best conveys the nature of a given strategy.

You’ll just have to squint, heh.

Summary

In this chapter we explored:

  • The nature of encapsulations
  • What makes code expressive
  • The tension between strategy and implementation
  • How to distill an implementation using delegation

In distilling our implementations, by delegating the “how”, we leave only the salts of the “what” remaining.

This allows us to more readily understand what the components in our software are trying to do so we can judge whether they are:

  • Even trying to do the right thing
  • And that they’re actually doing it

In the next chapter, we’ll deal with how our components acquire their delegates.