Clojure Gazette 188: Separation of concerns
Separation of concerns
Issue 188 - August 29, 2016
Way back when---back in the paleolithic era---the act of cooking was not so well distinguished from the act of eating. They were done in the same place (around the campfire), using the same tools, and often by the same people (everybody cooked). Over time, the two concerns separated. Cooking got its own places, tools, and people. Eating got its own places and tools. Each place and tool evolved to better solve the respective problem.
We can imagine a caveman encountering an onion for the first time. A problem presents itself: how to eat it? At first, the solution appears simple: take a bite. But the results are disastrous and result in a lot of tears and a runny nose. It's more complicated than the caveman thought.
He decides he needs to cook it first. He puts it near the fire, but one side cooks faster and burns, leaving the other side uncooked. He acquires another one, and thinking it wise to cut it in two, he begins beating it with a rock. It rolls away and is eventually smashed. He realizes that the sharper edge of the rock cuts it better. The next time he has presharpened a rock to cut even better. Taken to the extreme, this process of refinement ends with a high-carbon steel chef's knife.
As ridiculous as this story is, I find that it describes my process for programming all too well. At first the problem seems easy, then I learn that it's not and I end up breaking up the problem and developing refined tools to solve the problem. Those tools are abstractions.
Abstractions are solutions to general problems. The +
function is a
solution to addition in general. It is quite useless without arguments
to apply it to. The application of an abstraction to particular
arguments is a solution to a particular problem.
We often think of a piece of software as the solution to a problem, as in accounting software is the solution to the problem of accounting. But this is a bit like saying the kitchen is the solution to cooking. It doesn't tell the whole story. If you open the cabinet, you see that cooking is terribly complicated. It's made of a lot of smaller problems, like cutting and applying heat. Our software is the same. It's thousands of solutions to lots of subproblems.
When we develop a new system, our task is about as complicated as inventing cuisine. Our medium is much more maleable than steel, so we can iterate faster, but we still need to fill those drawers with tools and develop processes to select the right tool and apply it in the right way.
Sometimes I'll see a funny gadget on a TV commercial. I saw one once where you put an egg in the top, something cracks it, and the insides of the egg fall onto a little skillet. It was an all-in-one omelet maker. It was sold as a convenient device that would save you time and strain. It's easy to laugh at, but I've written many egg-cooker style abstractions in my day. It's a good analogy for separation of concerns.
The two concerns are easy to see: cracking the egg and frying it. They're both part of "cooking an egg". But somehow it's easy to imagine that a professional cook would never use something like that. Why wouldn't they? It's not because the purpose is too specialized. There are many small concerns that have their own tool in the kitchen. Take for instance the electric toaster. Its job is to dry out and brown sliced bread. Or the cookie cutter, whose job is to cut cookie dough into a certain shape.
No, it's not that "cooking an egg" is too specialized. It's not
specialized enough in the right way. The cook knows that there are n
ways to crack an egg and m
ways to cook it. That means that to have a
device for each combination would require n x m
contraptions. By
separating the concerns, one only needs n + m
tools, and by composing
them, you have just as many combinations. Slicing the problem of cooking
the n x m
way is just inefficient, though if you look back at the
cooking devices throughout history, many would seem equally bad.
The tools you need to cook an egg are likely useful for cooking other things as well. Modern cooks think about applying heat, down one level of abstraction, to get better control. The cook thinks about the medium the heat travels through, how quickly it transfers heat, how hot the medium can get, etc. And in the ultramodern kitchen, the cooks think about the molecular structure of the food. How proteins react to the heat differently from lipids. Instead of knowing about thousands of different ingredients, the cook thinks in terms of a few building blocks. And so the expert cook slices the domain of cooking down to finer and finer concerns, only to recompose them in new ways. The modern cook can quickly adapt to new ingredients simply by understanding what they are made of.
We do this same process, if we're lucky, to whatever domain we are coding in. But often we're not lucky and we're just cramming data into a database. I have, at various times, been fortunate to have learned domains very well just by virtue of developing systems for them. I got to contribute to the business's understanding of the customer and the market and give a slight competitive advantage to my employer.
Our systems need to be able to handle a domain at an expert level. They need to deal with separate concerns in ever finer and more nuanced ways. As you get further down the chain of abstraction, down to the proteins and starches, you find that your abstractions become more timeless. Proteins are going to protein, whether they're in a slice of meat or in a chunk of vegetable. This is where the most interesting developments happen. Keep pulling apart those concerns. There's still plenty of room at the bottom to explore.
Rock on!
PurelyFunctional.tv is the best place to learn functional programming. At least that's what I'm trying to make it. And I need your help! I need other teachers who want to share the great ideas of the past 50 years of programming and get paid to do it. If you're interested, please watch the invitation video .