Question

I recall a principle that a professor talked about while I was in school (back in the mists of time) that went something along the lines of "A method/function should either orchestrate other functions, or perform small, a specific task", but I can't for the life of me remember what the formal name of that principle is, beyond being a super-symmetric application of the Single Responsibility Principle.

Anyone know the name?

Was it helpful?

Solution

There is a principle that makes you do this but it doesn’t say it the way your professor did.

Every function orchestrates other functions. Even adding two numbers together and assigning them to a variable is orchestrating functions. You can twist yourself in knots thinking this way.

The single level of abstraction principle up holds the spirit of that idea but doesn’t require that you pretend you know where the bottom is. It only requires that you set a level of abstraction and stick with it.

That means this is not structural. It’s conceptual. It doesn’t matter if you mix functions from your own code, a library, or even the basic language functions.

What matters is that when you mix them together they don’t yo-yo your brain up to hand waving high abstraction and down to low level details all within the same function.

Do that and you’ll end up separating orchestrating and doing just fine.

OTHER TIPS

You may be thinking of Command Query Separation (CQS). It doesn't exactly relate to "orchestrating" vs "doing", but if this is from "back in the mists of time", maybe this is what you meant.

An excerpt from the Wikipedia article:

It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both. In other words, asking a question should not change the answer. More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.

Thus, the point is not only to focus on one job, but also to avoid mistaken modification while attempting to read data.

There's a principle called "do one thing and do it well" which may be related.

There's also the distinction between "pure function" (also called a "computation" or "calculation"), versus an "effectful procedure" (also called an "instruction" or "statement"):

  • A pure function only returns a result (that is its 'externally observable behaviour'). It may arrive at that result directly, or by calling other pure functions. Examples include performing arithmetic, branching on some data, constructing some new value, etc.
  • An effectful procedure will change the system in some way. Examples include printing some output, communicating with some external system (e.g. a database), changing the value of some externally-visible variable, etc. If a function returns an uninformative result, like void in Java, that's a good indicator that it's performing an effect (since there's no other reason for such a function to exist!)

It is a good idea to do as much calculation as possible using pure functions, since it is almost always 'safe' to call a pure function from anywhere in our code, without affecting any external system or any other part of our program (modulo edge cases like overflowing the stack). In contrast, we must be careful to only perform effects when it is safe to do so; for example, imagine calling a function to calculate some number, and it happens to also close a network socket! Effects can also alter the way other parts of our program behave, e.g. assigning a new value to a variable might alter the branches taken elsewhere in the code.

If a function/method does both of these things, i.e. performing some effects and returning some calculated result, it is said to be "impure". Such effects are called "side effects", since we may just want to do the calculation, but end up causing unintended effects as well.

This distinction appears in approaches like "ports and adapters"/"functional core, imperative shell"/"hexagonal architecture" and "pure functional programming".

Perhaps a better statement would be to say that a function shouldn't control flow and perform tasks. I don't know the formal principle behind it, but I usually use this technique in my own code.

void check() {
    if (dispensing) {
        checkDispensing();
    } else {
        checkNotDispensing();
    }
}

Then the sub methods would actually perform the tasks.

My $.02.

Licensed under: CC-BY-SA with attribution
scroll top