[email protected]
Engineering

Know Your Architecture

Coherent code in an Agile age

By

Illustration by Stephen Carlson

The joy that stirs in the beholder comes as much from the integrity of the design as from any particular excellences. As the guidebook tells, this integrity was achieved by the self-abnegation of eight generations of builders, each of whom sacrificed some of his ideas so that the whole might be of pure design.
—Frederick P. Brooks, “Aristocracy, Democracy, and System Design,” from The Mythical Man-Month (1975)

The vertical move, in which one leaves a role one has more or less figured out how to fill in order to apply for a role one could fill, almost, if one stretched, like a human-resource Virtuvian Man, is an acrobatic stunt meant only for the very brave or the very stupid. Being at least one of these, it’s a stunt that I’ve attempted twice.

My first leap, from a junior- to a mid-level engineer, was largely about confirming that I’d made mistakes — or rather, that I knew I’d made mistakes, and that I could reflect on and learn from them. In interviews, “what would you have done differently here?” was a fairly common line of questioning.

One that came up again in my second vault, from mid-level to senior positions, but in a subtly modified form. This time around, interviewers were looking less for hindsight than for foresight — for evidence that attention to the more ineffable factors that determine a software project’s outcome had been paid from inception. Some focused on stack, some on style; some on testing, some on tooling; but they all wanted to talk about architecture.

So much did this throw me at the time that a prospective colleague stopped one of the conversations to gently chide: “Talk a little less about folder structure.” I’m not sure I knew what the hell else architecture could mean.


Definitions abound. One, given by software éminence grise Martin Fowler in a 2015 talk, holds that architecture is “the important stuff — whatever that is.” Or, as a gloss on this same talk has it, “anything that’s important and hard to change.” Much to the chagrin of a certain stereotype of a software engineer, this is a wholly social judgment. There’s no hard-and-fast rubric for what makes something important; the important stuff is whatever those who know the code best agree it is.

The cheekiness here is useful for bringing architecture out of the highfalutin rarefied stratosphere, but also a little frustrating: it’s hard not to feel as though Martin knows something he isn’t telling us.

In software as in life, one thing that winds up being true about architecture is that it’s what you live in. Think about the code you interact with on a daily basis. Does it feel good? Is it easy to take pieces out and put pieces in? Does reading it yield a pleasant sense of order and clarity, or a discomfiting sense of chaos and muddiness? This is a little loose, as criteria go, but it works. It’s also easy to get a grip on: when talking to people who have some experience with the warp and weft of a codebase, they’ll know its tactile qualities more readily than they’ll know whether it’s well-architected in the abstract.

For my own part, when I’m kicking the tires of a new codebase and trying to evaluate its design, I look for boundaries and contracts. Boundaries are the liminal spaces where one part of the code touches another; practically, they’re where you’ll see patterns like exports and imports (in some languages), module declarations (in others), and filesystem demarcations (folder structure may not be the whole thing but it’s still a thing). Contracts are crossover spaces where one part of the code expects something from another; practically, they inhere in patterns like inputs and outputs, request and response shape, and ORM object structure.

More formal definitions often reach for a notion of a system’s “fundamental” or “highest-level” components. But this tends to draw focus to features like stack and language choice that bear on the architecture but couldn’t be called architectural per se. (A JavaScript project will tend to separate its modules into filesystem directories where an Elixir project may not, but both can and probably should be architected in a modular way.) At the same time, definitions like these pull focus from micro-level design decisions that have an outward-rippling effect on how the system is built.


Here’s the dirtiest littlest secret about software architecture: you’re already building it. In a certain meaningful sense, a software system’s architecture is nothing more or less than an accretion of the decisions made about it; and so any developer with a nonzero level of agency regarding their code takes on a piece of the shared responsibility for the overall design.

This is doubly true because design begets design: the style of your code affects the style of all the code that invokes it, imports it, or otherwise involves it. Did you choose a very object-oriented approach? Code that depends on yours will have to interact with your objects. Were you more functional? Depending code should be ready to compose your functions with its own. Even subtler details, like the shape of your methods’ parameters and the type of their return values, will nudge calling code’s own design in one direction or another.

Architecture, in other words, is everywhere. By expedient of a handy epistemological gotcha, even “no architecture” is an architecture in its own right. If your web project is an index.html file that grabs jQuery from a CDN, that’s an architecture. Design choices about which you should seek counseling are still design choices.

The question isn’t whether or not you’re architecting your software systems. The question is whether or not you’re paying attention.


And yet, comparatively few engineers make architecture enough of a focus to do it deliberately and do it well—which might seem to contradict the claim to its pervasiveness. After all, if we’re all always doing this work, architects should abound.

The difficulty is not that architecture is an especially complex or inaccessible problem, but that it tends to be a more human one than many engineers would like. The term conjures Lana and Lilly’s lapidary pen-tapping, neatly-pressed Helmut Bakaitis character, sat solo in a comfortable chair amid a sea of screens, bound only by “the parameters of perfection.” In reality, an architecture is a deeply dialectical phenomenon, involving both the dev team and the stakeholders in a conversation about the basic ontological nature of the system. An architecture works when we can hold its objects clearly and comfortably in our heads, and talk to one another about which parts of the domain they map and why. It’s established and expressed as much in conversation as in code.

It’s also a little finnicky and high-concept. Not everyone has the patience for Talmudic debates about why a Widget is not a Gizmo — which is good and useful, on a team, to have people driving towards praxis, but less good and less useful in an architect, who should be willing to dwell for a bit in theory.

At the same time, there are no fundamental laws of architecture, no fixed indelible rules; and so taste comes into play. Indeed, taste is probably more a factor than many dedicated architects would like to admit. One likes to think of one’s decisions as proceeding from reason, and taste is notoriously difficult to reason with.

But not, still, without its own internal logic. Whereas preference may vary from moment to moment, taste is something like preference over time — a consistent, principled application of judgment that yields similar results when applied to similar problems. Importantly, repeated applications of taste build on themselves: taste becomes good in those who apply it confidently and revise it ruthlessly.


This ought to be encouraging. Because the nice thing about taste is that it’s not fixed. You can improve it. Which brings us back to attention.

The simple act of paying attention is the royal road to becoming a better architect. Observe the decisions you’re already making. Introspect them. Try to understand why you think a function should take this parameter rather than that, or return that type rather than the other. Then test that understanding against your colleagues and your stakeholders. Design is revision, so you should be prepared to be wrong — wrong is often the best thing one can be, when it comes to learning — but also to reason about and defend the choices you make.

Because — and this, perhaps, above everything else, is everything else’s upshot — the choices you make matter. Take them seriously. If architecture is pervasive and you’ve been building it all along, then you have a personal hand in determining the shape of the system. It’s up to you whether you’ll be an attentive or a neglectful caretaker.

Code, finally, is reified meaning, and the architect and the engineer are the ones who reify it. Badly-architected code is like poetry with inconsistent meter — it means in the crude sense of signifying (your code may compile and execute), but its meaning is stillborn, under-realized, function without structure. It comes apart in one’s hands. Well-architected code may never be the rival of Hamlet or Lear, but it has all the merits of poiesis: in a flash of lucidity, it brings new meaning into the world.

 

Daniel is a lead engineer at Postlight and the founder of Ignota Media. He fears nothing, except for forks with wacky tines.

Story published on Jul 16, 2018.