October 21, 2022

Code patterns, 2022

I'd like to think that my coding style has evolved over the years. I've had my era of functional programming worship, and of promoting small modules. Trying to use the module pattern, before new ES6 syntax made it unnecessary, or Flow, before TypeScript made it irrelevant. Software engineering on the web moves fast: you can either be grumpy about it, leave it for a slower-moving field, or roll with the punches.

It's nothing groundbreaking, but here's where I land on some programming patterns now:

Using bigger files

Placemark's design system is just one file - components/elements.tsx. It's 902 lines and has 77 exports. Once a given file in this project gets over 1,000 lines, I consider splitting it into small pieces, but almost never is the length of a file, by itself, a factor in how things are distributed in the application.

Big files have a bunch of benefits:

  • You can reuse import statements. If you're using a lot of types or dependencies, each component might rely on many different imports. Following a component-per-file convention means that you have a lot more code dedicated to imports in your codebase. And sure, editors can manage imports for you, but the code's still there.
  • Less context switching when working on related code. For example, a UI component that relies on some specialized function to work - if the function is complex or long enough, it's tempting to put it in a separate file. But module boundaries are never that strong: most likely, you'll end up working on the two files at once, in tabs or an editor split pane.

There are few downsides to big files, in my experience. Whether your source files are small or large doesn't tend to make any difference for performance. At some point long files with lots of edits will get hidden in GitHub's Pull Request view, which is a downside. But I think the affects on readability and the ability to scan an application are overstated: code structure, naming choices, and module boundaries are what makes an application understandable or not, not file length.

Using lodash

I know. It's 2022. JavaScript has its own "Array.map" function. But it doesn't have nearly as broad a standard library as Ruby, and after experiencing what it's like to have an array object with max, uniq, and, well, you name it - all the functions - it's hard to go back. So I use lodash.

When lodash shows up in my stacktraces, I'll occasionally replace a lodash method with a hand-rolled implementation, but there are still many cases in which lodash is plenty fast and incredibly convenient. The ad-hoc implementations of methods you could get as part of lodash often have bugs, anyway: for example, the typical Stack Overflow answer for "how to get the maximum item in an array" will break once you have a few hundred thousand elements.

Using destructuring parameters for any complex method

Much like I don't have the patience to reimplement basic utility methods every time I need them, I don't see the point of remembering argument order beyond one or two. In every case, I'd prefer a method like otpUrl({ secret: totpKey }) instead of otpUrl(totpKey). I'll remember what the argument is, in this particular context, and if the method requires another 2, 3, or 8 arguments, then it'll scale nicely. So I use destructuring parameters really often, generally as soon as a function needs more than one piece of data. The performance cost is minimal enough that I would be surprised if it ever shows up in a flamegraph.

Prefer clarity over shortness, always

There are certain naming choices in the Placemark codebase that might shock you. A map is internally called a "wrappedFeatureCollection", and it's consistently called that, in function calls and elsewhere. A feature is a wrappedFeature that contains a feature, and likewise, those are spelled out.

Abbreviations, sometimes, but never will I blur concepts. A JavaScript Map object, a layer in a map, and a map, are separate concepts so they're kept separate, even if in casual conversation I'd refer to all of them as maps.

Be flexible with coding style

Some parts of Placemark use my fancy functional-programming types with purify-ts and higher-level functional-style programming patterns like mapping and reducing arrays instead of basic for-loop iteration. Others are written in a style that could easily be ported to C, and they're just concerned with performance. There's a place for both styles.

I try to fight the instinct to specify style rules. A codebase should feel cohesive and be readable, but there are many ways to do that, most of which have more to do with how one expresses and structures code, than whether a codebase uses all arrow functions instead of function declarations, or it keeps imports sorted in alphabetical order. Constructing my own bureaucracy of rules wouldn't help product development.