Good, fast, cheap. Pick all three.

From the beginning, macOS cleanly separated UI layout from application logic and data, allowing easy revision and maintenance of user interface elements, even by non-programmers. From the ResEdit Reference.

One of my favorite engineering one-liners is “good, fast, cheap: pick two”. Everything in engineering is a trade-off, and this one-liner encapsulates it beautifully. It’s even the subject of research papers.

However, my argument for bindinator is that it gets you all three in web app development. Isn’t this impossible? Yes it is, within a given set of assumptions. The fact is that bindinator lives outside the box of modern web frameworks because it is based on different key assumptions. Dare I call this a paradigm shift?

So, by changing the base cost and implementation time, you can essentially drive down the assumed time and cost to get a given level of quality, and you can raise the quality while keeping time and cost lower than without the paradigm shift.

E.g. compared to the 1940s, in the 1960s you could build electronic devices that, by 1930’s standards, were good and cheap, and faster to design and build than stuff done with valves. In fact you could do things with transistors that were impossible with valves. Which is how a $10 pocket-sized transistor radio was simply better in every way that a $400 mantle radio. Why? Because transistors changed the base cost, time-to-market, and quality of electronics.

Apple explains how user interfaces work. Weirdly, mac and iOS software is quite nice, and pretty easy to maintain, despite not treating the user interface as a “function” of state.

Similarly, the Mac architecture, HyperCard, NeXTStep, and the products they inspired, allowed user-facing applications to be developed quickly and cheaply, and yet be higher in quality, relative to the same thing rolled in, say, C or Fortran or even BASIC, which were the way you did stuff up to that point. A user-interface implemented in C with stdio couldn’t easily be improved based on user-feedback. The rm command has the same lethal shortcomings that it did in 1970, despite recognition of the problem almost from day one. Meanwhile if the default option in a dialog box of a Mac program could cause a disaster, you could change its wording (instead of “OK” and “Cancel”, “Cancel” and “Discard Changes”) move it (English-speaking users won’t jump to bottom-left), make it not the default (maybe , or just remove it while you revisit the design without touching any code.

In designing and refining bindinator, my goal was, and remains, to produce something better, simpler, more robust system than NeXTStep, not something worse than UNIX.

Meanwhile, the current dominant front-end development philosophy assumes:

  1. Dependencies are awesome. Use them without even thinking about it. Good tooling will automate everything. In fact, the number of readily available dependencies of a library is a sign of how awesome its “ecosystem” is.
  2. Transpilation is good because it lets you write code in a better language than JavaScript.
  3. JavaScript is a dynamic language and that’s a Bad Thing.
  4. User interfaces are a function of state.
  5. The cost of anything should is the cost of adopting and maintaining it, and we’ll assume the latter is pretty much zero because someone else is doing it.

The expensive consequences of these assumptions turn out to be exponential.

  1. You can never tell when changing any small thing will break everything, so you need to test like crazy. Also, the tests are slow and often flaky. Also, you’re going to want types. Lots of types. Now, if you make any significant change you’ll have to rewrite lots of tests, and you’ll create breakage in places you never thought of. Oops.
  2. The time between making code changes and seeing the results starts at a few seconds and then quickly snowballs until you end up with recompiles that take several minutes despite running on godlike infrastructure. This is not an exaggeration. I wrote extensively on this a few months ago, while waiting for some TypeScript code implementing something I’d have written, had reviewed, revised, and pushed to production, to go through a recompile along with the entire server it ran on, in Google’s cloud infra.
  3. You never learn how to actually code a web-browser and spend time learning and unlearning stuff that only applies to the world you’ve bricked yourself into. (You’re not a web developer, you’re a React, Angular, Vue, etc. developer, and you’re unlikely to change because learning that shit was hard.)
  4. The relationship between data, logic, and presentation, including both business rules and bindings, is intertwined and atomized. Small changes require lots of rework. Oh and tests. Also, the user interface is intrinsically unstable, so to ensure stability you need more testing.
  5. You can almost never shed dependencies. Everything you adopt becomes inextricably intertwined or, as a friend puts it, “cancerous”.

In short, web architects seem to have forgotten all the lessons of the first 30 years of modern GUI development. They’ve even forgotten the benefits of those lessons, to the degree that they aren’t even seen as a design goal. About eight years ago, Bohemian Sketch became the standard tool for web designers to design UI layouts. Today it’s Figma. They both do much the same thing, while neither has a round-trip to production software. You can’t just design a layout, save changes, and run the app.

Yet you could do this on the Mac in 1986 (which is, I believe, when ResEdit became generally available). Doing this even better was a core design goal of NeXT, and allowed small development teams working on NeXT platforms to build software better, faster, and cheaper than on other platforms. The core tool lives on in macOS as Interface Builder.

Because I lived through all this history, I refuse to forget all the best features of software development in the 1980s-2000s. So, bindinator was developed based on the following assumptions:

  1. Dependencies have a cost. Sometimes they’re worth it, but see 5.
  2. Requiring transpilation during development is unacceptable.
  3. JavaScript is a dynamic language and that’s a Good Thing.
  4. User interface descriptions and bindings are static data (and not a function of state). User interfaces are fixed and stable by default. Buttons don’t move around. Fields don’t disappear.
  5. The cost of anything should factor in the cost of throwing it away later.

The benefits of this are exponential, which is why bindinator can give you “all three”:

  1. Fewer dependencies means everything is smaller, lighter, and more maintainable. It’s easier to “get across” the system.
  2. The debug cycle is close to instantaneous and stays that way. How much more productive is a programmer with a 0.5s debug cycle vs. a 5 minute debug cycle?
  3. You are writing code that you can debug. How much more productive is a programmer who sees in the debugger the exact code they wrote? (But what about type safety? Well, I’ll get to that in another post.)
  4. User interfaces should be orthogonal to logic and presentation, and can be both switched out, modified, and reused cheaply, without side-effects. (In the dominant paradigm, user interfaces ARE side-effects.) How much more productive is a project team where someone can work on a user interface component entirely independently of someone working on the business logic?
  5. Non-modular components and leaky abstractions should be rejected because they immediately raise issues during assessment or review. How much more maintainable is a project where a dependency or module that proves to be a liability can be cleanly replaced?

In my experience, most enterprisey web-applications are rewritten from scratch at fairly short intervals, because they’re almost unmaintainable. And they’re unmaintainable because of the first set of assumptions. A dependency proves problematic, and getting rid of it is incredibly fraught, especially when all the much-requested-and-hard-to-deliver user interface changes in the backlog are taken into consideration.

Most of my assumptions aren’t original. Two of them (1 and 4) were founding assumptions of GUI development, e.g. in the original Mac operating system where an application’s business logic lived in the code fork, while window layouts were stored as individually editable items.

Assumption 2 was, essentially, a major design goal (eventually pretty much accomplished) of the Visual Studio team.

As for 3, JavaScript is essentially what Dylan dreamed of being: a flexible, more accessible LISP. When Dylan was announced at WWDC, the developers showed off a pretty decent Finder replacement a team member had written in a week. If you replace JavaScript with another pseudo-language without understanding what makes JavaScript great, you’re throwing out the baby with the bathwater.

Assumption 5 may be genuinely original. I’ve run it past a lot of people and that’s the feedback I get. The interesting thing about it is that it kind of gets you 1 and 4 as lemmas.

This is literally the same dialog (modulo “aqua”) from Photoshop 1.0. (It’s good that it still works, not that it’s still like that.)

These assumptions are how HyperCard and Visual Basic (and Delphi, kind of) worked at their core. This is what makes coding in Unity 3D such a joy. This is how an applications like Photoshop can have evolved and scaled over 30 years and remain dominant while still maintaining bits of user interface that date back to 1991.

Today, on a bindinator project, a designer can build and modify a view using vanilla HTML, and directly update project CSS without (much) worrying about side-effects or knowing how any of the code works (or even looking at that code). Heck, the hard part is teaching them how to work with git. And they can be treated and compensated like an engineer. A reasonably competent programmer can look at a view and immediately understand its bindings, whether looking at source or using the developer tools in the live application.