Subtasks vs Capabilities

There exists a myth about software: that we can start with what we want it to do, break that into subtasks and subtasks of subtasks that we can each specify completely, implement all those subtasks, and then assemble them into something useful.

Harold Abelson explains it in this classic lecture (h/t @pesterhazy for the link). He also explains the problem, and a solution.

He draws the hierarchy of subtasks. If we learn something and need to make a change at a spot in the middle, the work below it is all invalidated. And changes to that specification may ripple out to the sides, ruining everything.

Look at all these elegant subtasks, suddenly rendered worthless

That hierarchy of subtasks seems rational, but it is not practical. Instead, Abelson points out, the way things really get done is in levels of capabilities: fleshed-out subdomains of the problem. If we want to build graphics, then we need a capability of making shapes, and for that we need a capability of locating points. Each of these has its own vocabulary and implementation. Each is coherent and broadly useful, specialized to a problem domain but not a single task.

Abelson is working in a Lisp, and Lisp makes it easy to write mini-languages to solve each problem. So his capabilities work out to levels of language. Build a language to describe points and locations, then geometry on top of that, then combinations on top of that.

When something changes, chances are you can express the new need at one of the higher levels without changing anything further down.

The capabilities (his “language levels”) use each other flexibly.

These are capabilities. We need the capability to describe points, then a capability to assemble them into geometries, and on that we can build a capability of combining those into useful images.

The capabilities depend on each other, and they have specifications, but they’re broadly useful. They are built to a range of closely related purposes.

The same is true in our companies and societies.

A culture is like a big organization which assigns each of its members a place where he can work in the spirit if the whole…. In an age without culture… forces become fragmented and the power of an individual man is used up in overcoming opposing forces and frictional resistances.

Wittgenstein, Culture and Value

Here, Wittgenstein is very wrong. (To be fair, he didn’t publish this; the book is a posthumous collection of notes.) A culture certainly doesn’t break work into subtasks. But our companies do!

And it doesn’t work, either. You get rigidity, you get people held back by needing to go through others for specific subtasks. When infrastructure, purchasing, and marketing are elsewhere and we are required to delegate to them, this creates friction. The subtasks are never independent, so this doesn’t work even in theory.

Instead, create capabilities. To make useful software, we need infrastructure and purchasing and marketing. Create infrastructure as a service, so I can help myself. Create teams that consult on purchasing, and that provide advice on marketing. Yes, we need expertise in these, but not delegation. Delegation is blocking. Make products, all the way down.

Build teams and software as capabilities, not subtasks.

Smaller pieces, lower pain

Part of XP is breaking work down into the smallest possible pieces. Kent Beck teaches us to teeny tiny changes, changes so small that you don’t mind starting over when you get things wrong. Llewellyn Falco breaks work down into bits so tiny that most of them provable refactorings, minute changes like putting “if (true){}” around some code; adding an empty else statement; or calling a function that does nothing.

When changing a complex system, it helps to make each change as simple as possible.

When our limitation is cognitive load, the difficulty of the task is not the sum of the difficulty of the steps. It is the maximum difficulty of any one step.

At home, when I get stressed out, when the kids are talking to me and I’m trying to get ready to go and the kitchen needs clean and what is sticky thing I just stepped on — I catch myself, and start breaking down my work into the smallest steps possible. One tiny thing at a time.

Put down what I’m holding. Listen to the child. Ask the child to wait. Fetch a washcloth. Get it wet. Wipe the sticky floor. Put the washcloth in the bin. Put one mug in the dishwasher, and call the kitchen “cleaner.” One at a time, put away the things I was holding. Walk past the closet where my coat is, put on my shoes. Now get the coat. Put it on. Now get a hat. Put it on. Now get car keys. Now put the keys down. Put on gloves. Pick up the car keys. Ask the child to repeat the question.

This might take more clock time than if I try to answer the child and put away the stuff I’m holding and pick up the stuff I need while optimizing the route to avoid doubling back in the hallway. Maybe.

The tiny steps lower my cognitive load. This leaves me enough attention to hear the child’s question. It lets me handle the hardest single step (leaving the rest of the kitchen alone) without bitching.

Even easy things get hard when we lump them together. Add stress, and cognitive load is exceeded, leading to more stress, leading to things getting harder. Soon I’m yelling at the children, dropping a mug, and leaving without a hat.

Our limitation is not what we can do. It’s how much we can hold in our heads. So don’t push it!

In programming, it’s dangerous to work near your working memory threshold. You get more mistakes and more complicated code. In life, it’s stressful to optimize for fewer steps or fewer seconds on the clock. Do that when you’re bored; keep yourself entertained by straining your working memory. Only at home, please, not at work.

Reducing WIP in laundry and code

Today I brought up a load of laundry. When doing chores, I practice keeping WIP (work in progress) to a minimum. Finish each thing, then do another one. This is good training for code.

For instance, on the way up the stairs with the basket, I saw a tumbleweed of cat hair. I didn’t pick it up. Right now I’m doing laundry.

I put the basket on the bed, pulled out a pair of pants, folded it, and then stopped.

Do I put the pants on the bed and fold the rest? Or do I put the pants away right now, then start the next piece of clothing?

It depends which one thing I’m doing: folding this load of laundry? or putting a piece of clothing in its place?

It’s like in software development. Do we slice work by functionality or by layer?

Feature slicing, where we do all components (front end, back end, database, etc) of one small change and release that before moving on: this is like folding the pants and putting them away before picking up another item.

Layered work, where we first make the whole database schema, then develop the back end, then create the front end: this is like folding all the clothes and then putting them all away.

Pants on the bed are WIP. When clothes are on the bed, the cat sits on them and I can’t put them away. Then when I want to nap, my bed still has clothes on it. WIP is liability, not value. I can’t access my bed, and no one has clean pants.

cat on towels

Yet, folding the laundry and then putting it away is more efficient. I might fold three pairs of pants, and then put them away all at once. Four towels, one trip to the bathroom closet. The process as a whole is faster and smoother (excluding the cats).

Is layered work more efficient in software? NO. It always takes far longer, with worse results. A lot of rework happens, and then we settle for something that isn’t super slick.

Why is laundry different?

Because I’ve folded and put away this same pair of pants many times before. On the same shelf. Household chores are rarely a discovery process.

If I hadn’t done this before, then I might fold all of Evelyn’s pants in fourths. That is standard practice, and my pants fit nicely in my cabinet that way. When I go to put Evelyn’s pants away, I’d find that her shelf is deeper. It’s just right for pants folded in thirds. Folded in fourths, they don’t all fit; I run out of height.

Now it’s time for rework: fold all her pants again, in thirds this time.

With feature slicing, I would fold one pair of pants in fourths, put it on the shelf, notice that it doesn’t fit well, refold it in thirds, and find that it fits perfectly. Every subsequent pair of her pants, I’d fold in thirds to begin with.

Completing a thin feature slice brings learning back to all future feature slices.

For repetitive chores, we can choose efficiency. For new work, such as all software development, aim for learning. That will make us fast.

Smarter dishwasher, smarter cook

Today before breakfast, we started the dishwasher. I think they might have been clean already, but when I saw Avdi loading all the dirty cups from the counters in, I didn’t complain.

Today making scrambled eggs, I didn’t have my favorite spatula. It was in the dishwasher. Dang it! This is a danger of running the dishwasher during the day: some dishes are unavailable.

Then after breakfast, I couldn’t put the dirties in the dishwasher, because it was running. I had to leave them in the sink or wash them by hand.

My brother-in-law, Tyler, has a system for this. Every night he starts the dishwasher, and every morning he empties it. Not full? Oh well, start it anyway.

He always knows whether they’re dirty or clean, based on the time of day. He has access to all his favorite spatulas all day. He can load the dishes into the dishwasher as soon as they’re dirty. For added bonus: the dishwasher makes its noise while no one is in the kitchen. He can predict his usage of dishwasher pellets: one per day.

Why doesn’t everyone do this? Waste, I guess. The dishwasher uses water and electricity to wash a partial load of dishes. To avoid this waste, I maintain state: is it full yet? Are the dishes clean or dirty? Am I going to need this spatula before it’s done? Why are these dirty dishes in the sink?

What if the dishwasher is smart enough to use less water and effort when there are fewer dishes? The washing machine totally does this. What if we can take care of the waste by add complexity to the dishwasher, leaving my life less complex so I can concentrate on the scrambled eggs?

Maybe tracking all this makes me feel more in-control of the kitchen. Maybe it feels like part of the core work of housekeeping. But it ain’t like anybody’s standing there admiring it. The value I provided this morning wasn’t dishwasher management; it was scrambled eggs.

When programming, keeping track of memory usage used to be part of the necessary complexity. Now that we have efficient garbage collection, we don’t have to use brainspace on that. We aren’t less powerful programmers because we leave memory recovery to the garbage collector; we are more powerful because we can use our brains on the germane complexity of the business.

The system as a whole, with a smarter dishwasher or garbage collector, is more complex. But that’s fine, because there’s a boundary; someone else understands dishwashing or memory management. A lot of complexity in that component relieves me of a little complexity in kitchen management, and that means I can handle more complexity in my chosen domain. Or in programming, I can think more at a higher level because someone else is thinking hard at a lower level.

Starting tonight, I’m gonna let the dishwasher do a little extra work. Then I will concentrate on the work people care about, and make better scrambled eggs.

Basements and galleries

In a big new project, where do you start?

My dad bought this old building, and he’s turning it into an antique mall. It’s a huge project: two floors of shopping plus a gallery and museum at the top. Yesterday he showed me what he’s done so far.

In the basement, he added support beams and repaired the sprinkler system. They painted all the water pipes red, air blue, and electric gray. Paint is stored where it won’t freeze. The elevator machinery is oiled and accessible.

On the third floor, where art and artifacts will draw people through the store, he has crafted walls of sunrise, sunset, and nightfall from wood siding in dozens of different stains.

He started with infrastructure and with beauty. The underpinnings of the whole store, made observable with color coded pipes. And the unique features that will make this store different from any other.

In a big project, there’s not one place to start; usually we need to make little circles. Foundations, surface, various points in between.

Emphasize the parts that make the work smoother: a clean workspace, visibility into what’s happening, fire prevention. And the parts that make the work fulfilling: the beauty, so we remember why we’re doing this project at all.

A lot of rules

A Swede, on American Football: “Are there any rules? It looks like they stand in two lines, someone throws the ball backwards, and then it’s a big pile.”

Me: “141 pages, last I checked. It takes a lot of rules to look like there aren’t any.”

Later, people talked about error messages. There are so many different ones! … or there should be. Actually they all fall back to “Something went wrong” when the server responds with non-success.

Was it a transient error? Maybe the client should retry, or ask the person to try again later. Was it a partial failure? Maybe the client should refresh its data. Was it invalid input? Please, please tell the person what they can do about it. In all supported languages.

The happy path may be the most frequent path, but it is one of thousands through your application. As software gets more complicated, the happy path becomes unlikely. (99.9% success in a thousand different calls has a 37% happy path, if the person gets everything right.) What makes an app smooth isn’t lack of failure, it’s accommodation of these alternate paths. Especially when people are being humans and don’t follow your instructions.

Each error is special. Each chance to smooth its handling is precious. People rarely report confusion, so jump on it when they do.

This alternate-path code gets gnarly. You may have 141 pages of it, and growing every year.

It takes a lot of rules to make an app “just work.”

Every action has two results (Erlang edition)

Every action has two results: a set of side effects on the world, and the next version of ourselves.

I learned this from Erlang, a purely functional yet stateful programming language. Erlang uses actor-based concurrency. The language is fully immutable, yet the programs are not: every time an actor receives a message, it can send messages to other actors, and then return a different version of itself, instantiated with different state.

Here’s an example from the tutorial in the Erlang docs:

%%% This is the server process for the "messenger"
%%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
server(User_List) ->
    receive
        {From, logon, Name} ->
            New_User_List = server_logon(From, Name, User_List),
            server(New_User_List);
%%% ...
   end.

This defines a server process (that is, an actor) which receives a logon message. When it does that, it builds a new list of users including the one it just received and all the ones it knew about before. Then it constructs a new version of itself with the new list! (That’s implicitly returned from receive.) The next message will be received by the new server.

It’s like that with us, too. Today I made coffee, with a side effect of some dirty cups and fewer coffee beans, and a next version of me that was more alert. Today I checked twitter, with a side effect of nothing observable, and a next version of me ready to write this post. Now I’m writing this post, which will have side effects with unknown consequences, depending on what y’all do with it.

This works in our teams, too. Every task we complete changes the world, and it changes us. Maybe we add tests or implement a feature. In the process, we learn about the software system we participate in. Did we do this as a team, or will we catch each other up later? Is changing the software more safe or harder than before?

When “productivity” measures focus on externally-visible outcomes, sometimes the internal system is left in a terrible state. Burnout in people, “technical debt” in code, and a degeneration of the mental models that connect us with the code we care for.

The consequences of our work matter now. The next version of us matters for the whole future, for everything after now.

Let’s reason about behavior

When we learn math, geometry, and logic in school, we’re always talking about things that are holding still. The element is in the set or it isn’t. The angle is acute or obtuse or right or we can’t know. Things are related or not. A thing has a property, or doesn’t.

Code can have properties. A function has side effects, or doesn’t. Data is mutated, or never. An API call can be idempotent. Whole programs can have properties and we like to reason about them.

Yesterday talking with Will Larson on >Code (episode 142), he pointed out that once we move beyond a single process, especially when we go to microservices and have oodles of processes running around, we don’t get to talk about properties anymore. Instead, we have behaviors. He said:

Properties, you can statically analyze.
Behaviors, you can verify they happen.

A call to one program, or even a series of calls, can be transactional. Once you’re in a distributed system, not a thing. You can talk about how the system behaves.

Rein pointed out that in distributed systems, properties are incredibly expensive. Guarantees like all-or-nothing transactions, exactly-once delivery, consistency are never perfect in the real world, and the closer you choose to be, the more you pay in money and latency. Coordination is expensive.

In addition, can we get better at verifying and reasoning about behaviors?

Will pointed out that fault injection is a way to verify behaviors. That makes sense: psychologists learn a lot about the way we think from a few people with localized brain injuries. Emitting and querying events is another way.

Then how do we reason about behaviors? Systems thinking helps. Will recommends Donella Meadows’s Primer as a start. (I loved that book too.) Also, the social sciences have been studying behaviors forever. Maybe their methods, like Grounded Theory, can help us.

We’re people, right? We have behaviors. If we can get better at naming and reasoning about them, maybe we can get better at being people. It could happen.

Other people’s messes

There’s a funny thing, that when I walk into the kitchen and there’s sunscreen on the counter that I left there before yesterday’s bike ride, there’s a plate that I put to soak on the counter and there’s a book about Klimt that I had hoped to read with my coffee, this is fine.

It doesn’t feel messy. I almost feel a comforting sense of continuity with my past self.

But when other people leave crab rangoons and cups and bowls and mail and washcloths lying around, ugh! mess!

It’s somehow different when I have the story behind it.

Code is like this too. If I remember writing it, then I know this nutty interface name was a placeholder. This ratty console.log helped me debug a tricky crash. I’m in the process of refactoring to use the new functions, but the old ones are still around some places.

When that’s anyone else’s code, it looks like trash.

This tells me: if the code is my own toy, and I’m likely to be back to it before I forget who I was when I wrote it, then all those intermediate states are fine. They might help me get back into this groove, even.

But if the code is shared, or if I won’t be back in it later this week, make small changes completely. One at a time, in series, so that each commit leaves the code clean. Put the sunscreen on and put it away. Wash the plate and put it in the dishwasher immediately. Take my coffee upstairs to drink with the book.

Sometimes I wish I lived alone, so I’d only have my own mess to deal with. I don’t want to code alone, though, except on toy projects. I do wish I lived with people who cleaned up after themselves. I resolve to make smaller messes in our shared code, one at a time.

Mostly we orient

Observe, Orient, Decide, Act. This is the OODA loop, first recognized in fighter pilots and then in the Toyota Production System. It represents every choice of action in humans and higher level systems: take in sensory data, form a model of the world, choose the next action, make a change in the world.

At least in fighter pilots, and in all our daily life, most of this is automatic. We can’t help observing while we are awake. We constantly decide and act, it is part of being alive. The leverage point here is Orient.

The model we form of the world guides our decisions, both conscious and unconscious. Once the pilot has a geometric plane of battle in mind, the decisions are obvious. Once you see the bottleneck in production, you can’t look away from it. When I have an idea what’s going on in my daughter’s mind, I can talk to her.

Our power to change our actions, our habits, and our impact on the world lies in Orient. When we direct our attention to finding new models of the world, whole new possibilities of action open to us.

Fighter pilots can see what is possible when they picture the battle in the best geometric plane. Production managers needs to look at the flow of work. In software, I look at the flow of data through services and functions — different from when I used to see in objects or think about spots in memory.

The power of breaking work into smaller chunks is the chance to re-Orient in between them. TDD gives us lots of little stable points to stop and think. Pairing lets one person think about where we are in the problem space while the other is busy acting. Mob programming gives us the chance to negotiate an orientation among the whole group.

That co-orientation is crucial to collaboration. With that, we can predict each other’s decisions and understand each other’s actions. If we have a shared model of the world and when we are going, plus trust in the competence of our team in their respective specialties, that’s when we can really fly.

(This post is based on a conversation with Zack Kanter.)