Form is one of the hardest things to understand in software, mostly because it gets conflated with style and formatting. Style and formatting influence how people read and understand your code - you can bury good design in bad style, and you can make bad design look good with good style, but form is fundamentally an expression of design.
We struggle with this because there are many decent working programmers that have never actively engaged with the design of their software, so mistake cosmetic decisions for design decisions. The reason that form is so important in software is that it’s the thing that directly influences how people understand your code, and the tool you have to articulate your design goals, and the trade-offs you’ve made to achieve them.
From is the way you name your variables, the interaction style you outline with your APIs, the kinds of data structures you pass around your software. The intent of your software design is encoded in its form.
One of the more interesting aspects of form is the tension between regular and irregular form. Regular form is the default form of a language - it’s collection of idioms, defaults, “standard ways of doing things”, compared to an irregular form, where you diverge from form to achieve some other goal.
While people might not realise they’re responding to the form of a piece of software sometimes I’ll see people talk about things being idiomatic / non-idiomatic, or “feeling right” / “feeling wrong” - this is a response to the form of the software, and how it’s articulated. You see this everywhere in different programming language - python developers often talk about “pythonic” code, golang has a philosophy of emphasising “simple and minimal” code that trends towards repetitive, and multi-paradigm languages like C# will often face backlash when language features are introduced that bring in a new form of doing something that exists elsewhere in the ecosystem. All of these behaviours are people internalising regular form and responding to it.
Regular form, and regular style are a good way of helping people feel comfortable and oriented in software, but some of the most interesting software design comes from subverting regular form to achieve some other goal.
For example, ASP.NET middleware looks like this - taken from the official documentation:
public class RequestSetOptionsMiddleware
{
private readonly RequestDelegate _next;
public RequestSetOptionsMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
var option = httpContext.Request.Query["option"];
if (!string.IsNullOrWhiteSpace(option))
{
httpContext.Items["option"] = WebUtility.HtmlEncode(option);
}
await _next(httpContext);
}
}
This class can be discovered or passed to the framework at startup, and implements the “middleware” pattern common in web frameworks. If you’ve used ASP.NET Core, you’ll be familiar with these classes, but as a class, it’s actually pretty weird. The default, conventional way of thinking about classes in C# is that they should be stateful, and have methods that operate on that state. The middleware construct in ASP.NET Core neither follows that pattern nor is used in that manner - really just acting as a container for a function that gets invoked as part of the request pipeline. This is a subversion of regular form in C# to achieve a different goal - to make it easier to compose and reason about the request pipeline in a web application. ASP.NET Core and MVC in general are full of these subversions of regular form - so much so that they’ve become expected, regular forms of their own (Controllers are another example of a class behaving in a way you would not traditionally expect a class to behave, with lifecycles managed invisibly by the framework).
Framework authors often subvert regular form to express the design and intent of the frameworks they’re building and their features - despite using the same syntactic constructs as the code that lives in the “application space”.
There’s a wrinkle in this though - subverting regular form is a high-risk, high-reward strategy. It’s easy to subvert regular form and make your code harder to understand, or to subvert regular form and make your code harder to maintain. It’s a tool that should be used sparingly, and with intent. It also, by definition, requires a mastery of regular form to correctly subvert.
I went to see a great exhibition of famous film director and animator Tim Burton’s work recently, and one of the more interesting things was looking at the sketches that he did while training as an animator. Not because they exemplified his signature style (his own subversion of form) but because they were so textbook and quality. He had to learn what correct form was in traditional illustration, and why it existed, to be able to develop his own style that explicitly had something to say.
Burton is famous for esoteric stylised characters and settings, but he had to learn the rules before he could break them. All of the best people at a discipline occasionally subvert conventional form in design because regular form is the “best worst” and least contentious path. But to subvert form, you must understand what the most common form communicates, and find a way to do it better.
This approach to learning mastery before reform reminds me of the thought experiment of “Chestertons’ fence”:
"In the matter of reforming things, as distinct from deforming them,
there is one plain and simple principle;a principle which will probably
be called a paradox.
There exists in such a case a certain institution or law;
let us say, for the sake of simplicity, a fence or gate erected
across a road. The more modern type of reformer goes gaily up to it and
says, "I don't see the use of this; let us clear it away."
To which the more intelligent type of reformer will do well to answer:
"If you don't see the use of it, I certainly won't let you clear it away.
Go away and think. Then, when you can come back and tell me that you
do see the use of it, I may allow you to destroy it."
You shouldn’t change or subvert something until you know why it is.
Regular form is what drives conversations (that are mostly boring) around design patterns. Design patterns, popularised by the “Gang of Four” book of the same name, were intended as model answers to well known questions in software design of the time. They gave language and shapes to regular problem spaces. Unfortunately, as a side effect, people ended up fixated on their regular form, and “best practice” to their detriment. Many of the lessons dissolved into cross-generational knowledge held by people that never read the original works, who didn’t realise that they were presented as “things that we have observed working several times in specific contexts” and instead ended up as accidentally canonised designs.
It’s easy to think that I am advocating for “do whatever” design, but I’m not. Regular form - patterns and defaults, are valuable because they’re explicable to all. You are absolutely allowed, and should, with maturity as a designer, subvert regular form, but only when in your context you have something that achieves the same goals as the regular form, with better outcomes that says something about the design of your software, APIs, or modules. Making random non-default decisions is probably not smart, unless you’re an expert doing it knowingly and making an explicit trade-off that elevates or better solves for part of your design.
Footnote
Notes on the Synthesis of Form is a book by Christopher Alexander, a famous architect and urban planner. It’s a book about design, and how to think about design in a way that’s not just about making things look good, but about making things that work well. It was the original inspiration for the term “design patterns” in software. I invoke its name half in homage, and half as a knowing nod.