On Architecture and Modules

Another long Quora Answer

Why is it important to agree on software architecture principles?

In a sense, some of this is an update on my thinking on “modularity” (eg. ThoughtStorms:DecompositionByLanguageIsProbablyAModularityMistake

Well, possibly it’s only important in an “organizational” sense.

In that people in your team or project need to be aligned in their conception of the architectural principles or they’ll be working against each other or fighting.

OTOH, are absolute / global architectural principles important?

I’m inclined to think that it’s the same as in the case of “real” architecture. Of buildings and cities.

In architecture I’m a big fan of Christopher Alexander (inventor of “Pattern Languages”) The point about Alexander’s “Timeless Way of Building” is that it IS universal, but part of the universalism is intense awareness of local conditions. It champions the traditional and “vernacular”. It’s small-c “conservative”, in the Burkean sense. Believing that “what people have been doing a lot of around here for last few hundred years is probably a good idea”

OTOH, taking a bunch of ideas that have become successful in France and dumbly re-applying them in Brazil in the name of some bogus “universality” can be, at best ridiculous, and at worst disastrous.

So there are many heuristics and patterns that are useful, but have to be seen in context. How much abstraction and indirection you might need can depend on the kind of project, the language you are using, the type of user, type of interaction, platform the software is hosted on etc.

I increasingly believe that writing good software is like being a good butcher.

Butchery is about knowing where the natural joints of the animal are and carving along them. This allows you, in the minimal number of cuts with minimal effort, to produce the maximum useful pieces of meat.

The same is true of software. It’s about getting a feel for exactly where the natural module boundaries of your application are. What things need to be decoupled and how much decoupling / indirection is needed at each boundary.

Getting that wrong is what leads to expensive problems. And being “wrong” can mean both too close coupling of things that should be more loosely coupled with extra layers of indirection. And ALSO too much unnecessary indirection / abstraction between things that naturally should be more cohesive.

We tend to teach the importance of putting in abstractions / layers of indirection, but ignore the cost of doing it when we don’t need it.

People sometimes cite Richard Gabriel’s Worse Is Better as a rather vague principle. But read it carefully and a LOT of it is about exactly this problem. How much a function should expose the caller to its own failures.

The intuition we are all taught to cultivate, “Do the Right Thing” is that the module should protect the caller as much as possible. “Worse is better” argues that in this case, the module incurs too high a cost (in complexity) from trying to protect the caller from this failure. And that it’s both “worse” but, in fact, “better” to let the caller deal with the failure.

The important point is here is that the general principle is not “never protect the caller from your failure”. Nor is it “always protect the caller from your failure”. The important lesson is that “better” is to recognise what is the right answer in this particular situation.

If “agreed architectural principles” is taken to mean dumb applications of heuristics : “always use an extra abstraction layer” then they are worse than useless. They’re positively dangerous.

If the “agreed architectural principle” is “look at what people have been doing here for a decade, understand why, and follow that” then we’re on to something.

There are patterns that are nearly essential in Java. But pointless in Python.

Java and C++ are extremely similar languages in many way … BUT if you write Java in C++ or C++ in Java you are doing things very wrong.

They are not substitutes.

Java IS suitable for writing huge systems in a way which C++ just isn’t. If you try to write the kind of mega-application that Java is used for in C++ it’s going to be horrible. Juggling that much memory allocation by hand is intractable.

Use C / C++ for small, independent low-level programs, and glue them together in something else (eg. write small independent tools orchestrated within the operating system, or as libraries called from a Python script).

OTOH, trying to write the small programs for which C / C++ are good in Java is overkill. You are going to put too many abstract boundaries and the cost of garbage collection into something that should be smaller, simpler and faster.

One thing which is particularly egregious about Java (and the C++ heritage it comes from) is that these languages have an impoverished vocabulary for talking about “boundaries” between modules. They see classes and objects as a universal solution for everything.

In fact almost all programming languages have fairly poor vocabularies.

When we think of software in terms of architecture, or in terms of the “natural joints” we start to see many kinds of joints / membranes between the parts of our systems. With many degrees of permeability. Many features of languages are about this : the inlineability of functions, hygiene in macros, scope rules, “referential transparency”, data-hiding, lazy vs. eager evaluation of function arguments, the access rules for classes, synchronous messages to objects vs. asynchronous messages to actors, go-routines, sockets, the Unix pipe, internet protocols, microservices, integration “at the glass”, integration in the database, centralization / decentralization of databases.

All these are issues to do with “what kind of boundary is there between THIS and THAT?” How permeable is the boundary? How much do dependencies leak through? What obligations does it incur? What timing commitments does it need? How is the data communicated represented? How are errors checked and controlled?

We recognise this huge variety. But our languages often don’t. Most languages try to hide the variety behind a single principle : everything is a function. Or everything is passing messages between objects defined with classes. Or everything is an actor with async. messages.

While there is something very attractive about this simplicity and uniformity. Sooner or later you find yourself somewhere where the kinds of boundaries you want between the parts DON’T correspond well to the kinds of boundaries that your simple principle defines. You think actors and immutability are way cooler than mutable objects. And then you try to write a photo-editing program that applies filters to huge bitmaps.

So one architectural issue is that our languages try to enforce a single type of boundary when we want many.

The other is arbitrary and unnecessary boundaries. The biggest culprit is the difference between what is inside the program and what is outside it. Inside the program we have function calls, messages, possibly go-routines and internal queues / async. channels. Outside we have async. pipes from the OS. And synchronous socket communication. And a separation of database engine and cache engine. And search engine. And front-end web-server. And client. And server. And microservices. And XML-RPC and SOAP. And Amazon Lambda and similar “function as a service” etc etc.

Our program is divided into files. And various languages and frameworks insist that they should be divided within the file system in a particular way.

Our systems are arbitrarily divided by the underlying architecture of our platforms. Regardless of the natural joints of our applications.

I believe that one task for the next generation of languages is to be able to describe a range of boundary / membrane types and their permeability, timing and protocol requirements, all within a coherent and simple vocabulary. And where questions like “is this communication synchronous or asynchronous”, “lazy or eager” etc. are explicit “parameters” or alternatives within our language.

The other things these languages must do is transcend the “inside the program” / “outside the program” distinction. Today we have applications which have maybe a couple of lines of code doing calculations and other “business” logic. And a huge external “extro-structure”, often dozens of config files and MVC separated directories, to represent routing, caching, permissioning etc. architecture.

We need programming languages where the large scale architecture is a first class citizen of the program. Not something which has to be laboriously built around it. And when the program compiles, it compiles to not only object code, but to automated architecture : Puppet or Ansible scripts, Continuous Integration, containerization, orchestration of Kubernetes pods. This is where the DevOps revolution needs to end up. Languages that are as fluent in talking about all the parts of our systems, and all the kinds of communication between them.

So why am I talking about this in a question about “principles”?

Because software as an art / discipline / profession / culture advances when we take informal ideas and turn them into formal code that can be executed. Heuristics and “good practice” become patterns, become libraries, and ultimately become language features.

Architectural principles will ultimately be recognised and “agreed upon” parts of our practice when they finally become part of the languages we use everyday.

Conway’s Corollary

Ian Bicking’s post on Conway’s Corollary is a must-read thought on isomorphisms between the organization and product structures.

What, asks Bicking, if we don’t fight this, but embrace it. Organizational structures are allegedly for our benefit. Why not allow them to shape product? Or when this is inappropriate why not recognize that the two MUST be aligned and if product can’t follow organization, we should refactor organization to reflect and support product.

Modularity At Fine Granularity

Ian Bicking has a fascinating question. I’m just going to quote the whole thing because it’s so good and important : 

The prevailing wisdom says that you should keep your functions small and concise, refactoring and extracting functions as necessary. But this hurts the locality of expectations that I have been thinking about. Consider:


<br />function updateUserStatus(user) {<br />  if (user.status == "active") {<br />    $("<li />").appendTo($("#userlist")<br />      .text(user.name)<br />      .attr("id", "user-" + user.id);<br />  } else {<br />    $("#user-" + user.id).remove();<br />  }<br />}<br />

Code like this is generally considered to be terrible – there’s logic for users and their status, mixed in with a bunch of very specific UI-related code. (Which is all tied to a DOM state that is defined somewhere else entirely — but I digress.) So a typical refactoring would be:


<br />function updateUserStatus() {<br />  if (user.status == "active") {<br />    displayUserInList(user);<br />  } else {<br />    removeUserFromList(user);<br />  }<br />}<br />

With the obvious definition of displayUserInList() and removeUserFromList(). But the first approach had certain invariants that the second does not. Assuming you don’t mess with the UI/DOM directly, and assuming that updateUserStatus() is called when it needs to be called, the user will be in the list or not based strictly on the value of user.status. After refactoring there are functions that could be called in other contexts (e.g., displayUserInList()). You can look at the code and see that particular things happen when updateUserStatus() is called, but it’s not as easy to determine what is going to happen when inspect the code from the bottom up. For instance, you want to understand why things end up in

    — you search for #userlist but you now get two functions instead of one, and to understand the logic you have to trace that back to the calling function, and you have to wonder if now or in the future anyone else will call those functions. The advantage of the first function is that blocks of code are strict. You execute from the top to the bottom, with clear control structures. When GOTO existed you couldn’t reason so well, but we’ve gotten rid of that! (Of course there are still other exceptions.) It’s not entirely clear what intention drives the refactoring (besides adherence to conventional standards of code beauty), but it’s probably more about code organization than about making the control flow more flexible. Extracting those functions means that you now have the power to make the UI inconsistent with the model, and that hardly seems like a feature. And I have to wonder: are some of these basic patterns of “good” code there because we have poor tools for code organization? We express too many things with functions and methods and classes (and perhaps modules) because that’s all we have. But those are full of unintended semantic meaning. Anyone have examples of languages that have found novel ways of keeping code organized?

    So, it’s a great question on modularity where we tend not to have much explicit thinking : down at the smaller granularity (compared to all the patterns for classes etc.) My immediate comment is that if Ian refactored his code like this :


    <br />function updateUserStatus() {<br />    var id = "user-"+user.id;<br />    if (user.status == "active") {<br />        addToList("#userlist",user.name,id);<br />    } else {<br />        removeFromList("#userlist",id);<br />    }<br />}<br />

    it would solve most of the problems. In this version we aren’t fussily creating extra functions for tiny fragments of functionality which are only relevant to narrow situations (ie. users, userlists). Now the new functions are more generic and widely applicable. They’re doing enough that it’s worth the overhead of creating them. They’re still usefully hiding the bit of complexity we DON’T want to think about here – the actual jQuery / HTML details of how lists are constructed – but they’re leaving the important details – WHICH list we’re updating and what parts of a user we show – in this locality rather than allowing it to become diffuse across the program. Of course, we can’t prevent another bit of code updating the list itself somewhere. (That’s more a quirk of the HTML environment where the DOM is global. In many analogous cases we could prevent most of the code having unauthorized access to a list simply by making it private within a class.)

    Modules In Time : Synthesizing GitHub and Skyrim

    Thanks to Bill Seitz I picked up on a Giles Bowkett post I’d missed a couple of months ago which compares the loosely coupled asynchronous style of development that companies like GitHub both promote and live, with the intensely coupled synchronous raids that occur in online game-worlds.

    Bowkett seems confused by the apparent contradictions between the two. And yet obviously impressed by the success of both. He wants to know what the correct synthesis is.

    That really shouldn’t be so hard to imagine. The intense coupling is what happens in pair-programming, for example. Or the hackday or sprint. Its focus is on creating a single minimum product or adding a single feature / story to it.

    The right synthesis, to my way of thinking, is intense / tight / adrenalin fuelled / synchronous coupling over short periods, where certain combinations of talents (or even just two-pairs of eyes) are necessary. And loose / asynchronous coupling everywhere else. Without trying to squash everyone’s work into some kind of larger structure which looks neat but doesn’t actually serve a purpose.

    The future of work is highly bursty!

    It shouldn’t surprise us, because modularity is one of the oldest ideas in software : tight-cohesion within modules of closely related activities. Loose and flexible coupling between modules. It’s just that with these work-patterns we’re talking about modules in time. But the principle is the same. The sprint is the module focused on a single story. The wider web of loosely asynchronous forks and merges is the coupling between modules.

    To return to a theme I started many years ago, I commented on this excellent article about why web-site development has got so damned hard. (And remember when we all thought of web-apps as lighter and simpler than desktop apps? What happened?)

    Anyway, here’s my comment.

    I think the problem is less the multiplicity of programming languages, than our insistence that we should always be separating our languages in different places.

    This goes against the basic tenets of cohesion and coupling. We cluster unrelated activities together because they happen to have the same syntactic sugar, while separating tightly-coupled activities because half of them happen on the client and the other on the server. Why the hell should this implementation detail have to be reflected in our architecture?

    What I’d like, controversially, is to be able to mix-and-match the languages within the same source file, grouping together the python, javascript, html and sql that actually has to work together in one place. I have no trouble dropping into regular expressions or similar DSLs from inside my main code, why should dropping into a layout or query language be different?