Practical frontend philosophy - jaredgorski.org

An illustration from Daniel Burnham's 1909 Plan of Chicago

Practical frontend philosophy

NOTE: This is a rather general post, written for people curious about how to think about "the frontend". However, I think it's relevant to interested humans of all knowledge and skill levels.

Last week I wrote a post called "Practical frontend architecture". It got some unexpected love on Hacker News and made the front page. Now that plenty of people have read that post, I wish I could rewrite it.

For one, despite its grandiose title, the post narrowly focused on our enterprise-oriented use-case at Liferay Cloud and a few patterns we've implemented during our recent stack upgrade. In hindsight, I would've rather detailed more of my architecture-specific thoughts. For two, that meant it was "yet another React + Next.js tech post" on the internet.

Is there anything wrong with React + Next.js? Maybe. It depends on what you're building.

Understanding the frontend

Frontend cannot be exempt from the rigor and sobriety of good software making. After all, frontend is software. "Frontend" really just means "software which supports an interface", and interfaces convey data. There are plenty of interfaces throughout a software system, including on the backend.

Everywhere data is exchanged, there is an interface. Each interface is designed to fulfill a specific set of data-exchanging needs. Backend interfaces tend to fit computer-oriented needs; frontend interfaces tend to fit human-oriented needs. Ultimately, computers are valuable because they help humans. So, since frontend supports the interface between computers and humans, a well-constructed frontend is a vital part of the greater hardware-software-human relationship.

Frontend interfaces may serve a different kind of consumer from backend interfaces, but frontend systems should not be built any less carefully than backend systems. Insofar as frontend code sucks, backend systems cannot provide value to humans.

Backend is often obvious

In my experience, backend is relatively obvious. When building a backend, needs are clear and technical solutions tend to appear straight-forward:

We need to move this file that computer. Hey! I say we use the move-file-to-that-computer tool. Last I checked, it moves files to that computer.

... I ran the tool, but the file isn't on that computer. The move-file-to-that-computer tool must be broken. Maybe it's the move module. Or I guess it could be the that-computer adapter.

Backend engineering mostly involves plugging already-made-things into each other and writing minimal, clear glue code. The trick is to wisely select quality already-made-things, keep those things as simple as possible, design an obvious and practical system which capitalizes on the strengths of those things, and, when custom things need to be made, make them as clever as they need to be, and no cleverer.

Sometimes, entire programs must be written from relative scratch. Complex problems may arise, like multithreading, making it necessary for the engineer to figure out whether message passing between threads is an appropriate solution. Yet, "the principles" generally dictate that software should be modular, obvious, and simple, with interfaces striking the proper balance (per use-case) between open and closed.

Frontend is easily overengineered

In my experience, frontend systems are often criminally overengineered. It's not good, but there's a good reason why this happens.

The frontend manages a giant data transformation. Frontend systems, when conveying data via their interfaces, often need to significantly transform that data to turn it into visual information. Let's list a few examples of significant data transformations from around the world of computing:

In case you didn't notice, that last item summarizes the sole purpose of most frontend systems that have ever or will ever exist. Not memes; displaying data on a screen.

It's no small task to turn scalar data into visual information. Fortunately, hardware (to us, anyway) does most of this job for us. However, the complexity involved necessitates an entire family of presentation languages, each with their own ways of getting the job done, depending on their purpose.

For example, HTML is a ubiquitous markup presentation language which enables engineers to represent data on web browsers. Some other presentation languages are really just object-oriented programming languages, like Objective-C or Swift, both of which act as an SDK for engineers to manipulate iPhone hardware and manage views. The need to organize presentation logic has given way to many popular patterns for displaying entire applications on screens, such as model-view-controller (MVC, i.e. "data source, presentation code, logic") and model-view-viewmodel (MVVM).

An aside: frontend modules should be "dumb". There's a reason frontend interfaces are commonly referred to as "presentation layer"; the single responsibility of frontend interfaces is to present the scalar data they consume to a web browser or other UI, and this presentation responsibility is cumbersome enough without adding data transformation to the equation. Frontend code shouldn't be tasked with scalar data transformations; that's the job of backend code. Therefore, application state management should be pushed "back" onto the serving interface(s) as much as possible. If frontend state management seems necessary, the backend interface should undergo review. If it truly is necessary, as may be the case with complex apps relying on highly dynamic data, the data should be consumed and represented in a straight-forward manner, as a "service layer" between the backend and the presentation code. The frontend's job is presentation, not calculation.

Visual data is far more complex than scalar data. Deducted from: art museums exist. Let me know when a number museum pops up and I'll recant. There's one well-known way to add numbers, and that's enough. There are a million ways to display colors and shapes, and we want more. Therefore, "new ways" of displaying colors and shapes on web browsers are always popping up. That's both totally fine (if you like neumorphism, shine on you crazy diamond) and kind of problematic (gimmicks galore). But one thing is for sure: frontend involves more possibilities than backend.

Redundant solutions reveal the problem space

The complex nature of visual data and the vast world of possibilities associated with displaying it mean that people will invent (discover?) many different ways of doing the same thing: conveying data from computers to humans. This adds noise to the world of frontend, making it more difficult to find productive patterns among the multitudes of antipatterns.

While productive frontend patterns are difficult to discern, we can already understand the problem space as it exists by observing redundancy cycles in the history of frontend technologies. There may be fresh problems to solve as technologies advance, but there's nothing new under the sun. There are only so many needs present within web-bound systems, which means there's a limit to the ways that those needs can be effectively met. Discerning the good ways is the key to understanding the boundaries of the frontend problem space and, therefore, developing effective patterns and solutions.

One redundancy cycle we've seen is templating engines. 20 years ago, there was PHP. 12 years ago, there was Mustache. 8 years ago, enter React (the coolest thing since sliced bread) being gratuitously applied to way too many should-be-static blog sites to count.

There are only so many good ways to solve a given problem. Gratuitous-React-sites is the case in point: we're witnessing far too many bad ways.

The walled gardens problem

A commenter on my previous post said this:

I think that's my pet peeve with modern frontend tooling, they feel like walled gardens of arbitrary knowledge that only applies to their ecosystem rather than to some fundamental learning about software.

This is a documented problem and it's not new. The same was true of jQuery UI (not that I was around for it) and the same will be true of the next wildly-popular all-encompassing toolset that promises quick wins to web developers.

Don't know what "hydration" is? That's fine. It's completely specific to how "dynamic templating engines" like React work and doesn't make sense outside of that sphere. If you just learned about hydration, or most any other React-like concept, consider that it's likely esoteric. In other words, don't try to tell your local FORTRAN fanatic that you're "hydrating" your website... he'll look at you funny.

Knowing "what hydration is" might not make anyone a better software engineer, but knowing why it's important in React just might. React is a cool tool made by smart people who decided that the word "hydration" should refer to their good idea of attaching event handlers to pre-rendered HTML rather than re-rendering it outright, improving first-load performance on server-rendered React apps.

Good engineers don't just use tools, they do things.

Don't use a tool, do a thing

Jargon isn't impressive; it often obfuscates more than anything else. Let's be clear.

React is just a fancy way to throw HTML together in the browser in certain ways depending on dynamic data. Vue, Angular, and Svelte do the same thing, but in slightly different ways from React.

Svelte is interesting; let's demystify it real quick. When Svelte detects changes to the dynamic data being displayed in the HTML, it actually makes surgical changes to the HTML based on where that data exists in the DOM. This is different from React, which actually maintains an entire virtual copy of the DOM containing references to the dynamic data being displayed. React continually interprets the code and, when data changes occur, runs a diffing algorithm to detect which parts of the HTML should be re-rendered from the virtual DOM.

If you're architecting a frontend which must display dynamic data, the above knowledge is relevant. But, at the end of the day, always remember: it's just a fancy templating engine made for dynamic applications. Quality tools which improve our ability to concatenate and interpolate HTML, whether statically or dynamically, are to be welcomed and praised. But we can never forget what we're actually doing behind the scenes.

Don't use React; render HTML.

Don't use tools. Do things.

Taking code seriously

Writing code is serious. Writing code makes things happen. Adding code to a system often means adding complexity, obfuscating the system's purpose (longer sentences tend to be less focused), or jeopardizing security. Writing code should be a sober undertaking in which all relevant factors are considered before making a decision.

Hammurabi's code?

From The Zen of Python:

There should be one-- and preferably only one --obvious way to do it.

I sometimes wonder if the world of frontend might benefit from a complete reset to some sort of Law or Code which compels frontend engineers to forsake all vanity and value strict adherence to a few boring, yet effective, principles.

Fortunately, we do have the makings of a rather informal Frontend Code. Firstly, sensible defaults exist. The user-agent styles in browsers are, for the most part, perfectly accessible and appropriate. There has also been significant work done to craft effective frontend design patterns and promote an accessibility-minded approach among frontend engineers at large.

However, innovation and the frontend are rather synonymous, and that's a good thing. Rather than attempt to curb the dynamic nature of frontend, we should prepare engineers properly to manage the chaos gracefully. Engineers who work on frontend systems must be doubly discerning, sober, and responsible in order to offset the realities of the frontend problem space.

If you don't know what it does, you're not allowed to use it

One rule I learned early on: don't copy and paste code from StackOverflow unless you know what it does. In the spirit of "don't use tools; do things", I think this principle can be generalized:

If you don't know what it does, you're not allowed to use it.

Writing design docs is (or should be) a fundamental step in any software writing process. Likewise, articulating "what it does" should be a fundamental step in any software tooling selection or architecting process.

Make intentional decisions. Using React, running service workers, or taking advantage of <insert obscure browser API> doesn't make a web app good. Potentially, quite the opposite: using technologies unnecessarily and without intention is tantamount to socks and flip-flops. Don't do it. (Unless you understand what you're doing and want to anyway, in which case, go hang out with the neumorphism people.)

You probably can't go wrong with a plain HTML+CSS website, anyway.

Ethos: form vs. substance

Marketers probably know us better than we do. This consumer psychology study is one of many examining the link between a human being's self-concept and the stuff they buy. What we consume offers a glimpse into what we value, who we think we are, and who we want to be.

Who do frontends think they are? Who do frontends want to be? Who is't art thee, O fickle frontend?

Frontend is sexy, which can be bad

Frontend engineering mostly involves posting pics of your morning coffee while you sling React components around and cache everything globally with no regard for proper state management. Alright, that last sentence was me just flinging paint against the wall. But there's a ring of truth to it.

I was unable to find hard numbers to back this up, but my perception is that frontend is a more ripe target for new engineers than backend. The frontend feels familiar. Building websites is fancy and fun. You don't need to understand database technologies or gain a feel for legacy systems; everything you need to know is available in a series of Instagrammable blog posts.

For new frontend engineers, particularly those like me who didn't study computer science, the frontend world is flashy, exciting, and accessible. Need to run build-time tasks? There's a package for that. Need performant algorithms without learning any algorithms? There's a pretty bulky package for that too. Need to check if a number is 10,000? There's even a package for that.

It's easy to install magical NPM modules, meaning it's easy to build a giant, vulnerable tree of unnecessary dependencies weighing your app down. NPM has found itself adjacent to controversy in recent years, but NPM is just a tool that humans use. There's nothing wrong with NPM itself; it's the humans. Humans are the X factor and, unless we know better, we humans gravitate toward shiny things.

I'm not convinced that tools made to help working humans craft better software should be marketed as if they're consumer-oriented. Mature engineers know that a tool is only good if it accomplishes the correct task effectively. If anything, tool-hype distracts less mature industry members (both tool users and toolmakers) from the ultimate goal of software engineering: building useful things.

The sexiness of frontend is yet another reason why engineers, particularly new ones, must possess particular wisdom and sobriety if they are to develop effective systems. Frontend-focused humans should be careful and pragmatic, and frontend culture should reflect and foster these traits.

Ragging on serverless real quick

It's not just the frontend. Sometimes backend tries to be sexy, and it's noticeable when it happens. "Serverless" is an example.

The misnomer (read: confusing, jargony, false promise) that is the word "serverless" really just means "a cloud company only charges you for the resources you use because they de-provision your backend after a timeout". There are still servers involved; it's just called "serverless" because there isn't any single server working solely for you. If anything it should be called "way more servers than before". A major downside of serverless is that your "serverless" programs will be put to sleep if they're not constantly used, leading to extreme startup latency when real-world usage doesn't match the configured wakefulness.

Anyway, you wouldn't know it by the name, but serverless is useful... depending on the job.

No golden hammers

It's been said before and I'll say it again: use the best tool for the job at hand. Is your tooling sexy and exciting? Take special care to make sure it's not impractical.

At the end of the day, an application probably does something pretty simple, and it probably does a number of other fairly simple things to accomplish that purpose. Think critically about the job at hand and attempt to understand everything that must happen to fulfill the requirements. Remember, if you don't know what it does, you're not allowed to use it. If you don't understand what you're doing and why, you're not allowed to do it.

Zoomed out, the frontend is just a data pipeline, conveying data from computers to humans.