Learning from the Golang Philosophy as a JS Programmer

These are a few ways that writing Go this year has changed how I think about writing JS.

My background as a software developer

I’ve been writing Javascript since 2007 or so, daily as part of a full time job for the last 7 years.

Golang started to pique my interest around February or March of 2020, almost as soon as things began to shut down because of COVID. It was my intention to learn more even before the pandemic started, so I guess in a way I’ve been lucky to have something to keep busy with. My affinity for distributed systems design lead me to Golang. I audited the wonderful 6.824 Distributed Systems course available via MIT (https://pdos.csail.mit.edu/6.824/) this year and Go was a perfect fit for the assigments. I’ve really enjoyed Go this year and it’s been a joy learning the idiomatic way of doing things. The official Go blog and this marathon video featuring Dave Cheney were great for learning why things are done the way they are.

To my surprise, a lot of the Go philosophy regarding project structure, interfaces, code organization, and naming conventions have started to carry over into my JS code. Some of these concepts aren’t exclusive to Go, and none of this stuff is new. I’ve followed these rules before when writing JS, but Go has given me reason to emphasize them more when writing or planning code.

Really Separating Concerns

Higher emphasis on separation of concerns at the module level.

Go’s code is organized into packages. A package is a directory (including the root directory) within the project that can contain multiple files. Packages are similar to modules in NodeJS in that they get imported by other packages and you can determine which variables and functions can be imported. In other words, they provide a level of encapsulation. With NodeJS, it’s typical to design your library to have a single entrypoint (usually index.js in the root of the project). That single entrypoint would be responsible for providing an API to the rest of the library. This isn’t the case in Go. Go’s URL import paths let you import single packages from remote repos.

Part of Go’s ideology is that projects should be a collection of separate packages – each package useful without the others. A direct nod to the UNIX philosophy. The Golang repo is a great example.

I’ve started thinking of my JS modules and directories within the project the same as I would in a Go project. That is, writing my modules as if they were standalone projects. In practice, it means variable and function names and comments that are more generic, with less references to the rest of the project. Again, nothing new, but if I was putting 70% emphasis on this before, I’m putting 95% emphasis on it now. It influences my planning moreso than before.

Module-Level API

I’m also putting more thought into the developer ergonomics surrounding my module. It’s shifted my focus to “what will this look like when it’s used in another module?”. I know the function will be namespace’d when it’s used in another module, so I can keep it’s name short and otherwise ambiguous, rather than long and specific. For example, a module named ‘config’ should export a method called get rather than getConfig because when it’s used elsewhere it’ll probably be invoked as config.get. config.getConfig would just be tedious.

I’m also thinking more about my module names. Will this module name, when imported, step on the names of other libraries, functions, or variables? It’s not a great idea to create a module named console.js, because you’re pretty much forcing everyone else to alias it whenever they import it. With that same regard, I avoid generic module names like ‘util’, ‘common’, ‘tools’. These always end up being aliased to provide a more specific name. The io/ioutil package in Go is a great example of keeping a utility package’s name specific to prevent naming collisions.

Flatter File Hierarchy

One of the Go idioms is organizing your project in more of a horizontal file structure. Some Go projects take this to the extreme and only have 1 or 2 directory levels. NodeJS projects in general are relatively flat but after playing with Go for a while I’ve noticed that my new projects are flatter than before. A flatter filepath means my import paths are shorter and the module’s relationship with the others in the same directory is more explicit. I like when my import paths don’t cause my line to extend beyond 80 chars.

Clearer Dependency Tree

I try to follow, to the best I can, a general rule that modules deeper in the directory hierarchy should not import higher-level modules. I think I started doing this more after writing Go code because of the package system. Here’s an example structure:

├── index.js
├── http.js
└── handlers
   ├── user.js
   ├── profile.js
   ├── handlersutil.js
   └── components
      └── input.js

It’s reasonable to assume that anything in /handlers may import input.js. It would be less intuitive if input.js imported, say, handlersutil.js. It’s easiest to think of the dependency tree as a directed graph and it’s harder to mentally visualize the graph when there are cycles. NodeJS allows circular dependencies which means a lot of developers aren’t cognizant of how things need to be structured to avoid cycles either now or in the future. The deeper a file’s path, the more generic the code should be.

There are exceptions. If there are generic files that are used throughout the project, they would appear in or close to the root of the structure. In the example above, user.js might import http.js. It’s easier to think of each different directory as a separate package (/handlers would be a package), and then avoid importing a higher-level module within the same package.

Those are a few of the ways my thinking of code has changed since I started writing more Go this year. Nothing groundbreaking, and most of this is stuff I was doing to at least some extent already, but I’ve changed how I prioritize it all. The motif is developer ergonomics and empathy for the consumers of the code I write. Most times these days that means my coworkers who work alongside me in the same repositories. If you haven’t realized, fiddling with a language other than the ones you work with daily is a great way to expand your thinking as a coder. It’s akin to taking a philosophy 101 class your first year of university and seeing the playing field from a differnt vantage point.