No language, framework, tool, technique, person, or computer program operates on its own. They are all part of a system, and that system exerts influence over all that it encompasses. I want to talk about this concept in the context of various best practices and aphorisms.
Experienced developers can benefit from highly compact or highly abstracted code. But, is this the best way to write code when considering the overall system, which incudes the business, team, and recruiting process?
It could be that verbose codes that is built from fewer concepts may sever the system better, even if it feels a bit too simple for the experienced developers.
Here is a Ruby construct that I would probably write without hesitation:
# List all unconfirmed users' emails
User.where(confirmed_at: nil).map(&:email).each do |email|
puts email
end
A developer that "knows Ruby" could be safely assumed to know what map
, each
, do
and the pipes around email
do. It's hard to write much Ruby without needing them.
But that &:email
- what is that? It's extremely hard to search for, even if you might reasonably guess at what it does.
Of course, if you don't know Ruby, none of it may make any sense! Here's a way to write this in JavaScript:
User.where({ confirmedAt: null }).map( (user) => {
return user.email
}).forEach( (email) => {
console.log(email)
})
If you don’t know JavaScript, but know Ruby (or a language that has list comprehensions like map
), you can have a good guess at what this does.
If you don't know JavaScript, however, you might wonder what does (user) =>
do? This, like Ruby's &:email
, is somewhat hard to search for if you don't know to call it an arrow function.
Here's another way to write this code:
for(const user of User.where({confirmedAt: null })) {
console.log(user.email)
}
What's interesting about this is that it doesn't require knowing what map
does, what it means to pass a function to another function, or what an "arrow function" is. If you never saw JavaScript in your life but were experienced with probably any other language, you would understand this.
Most of us programmers think of "accessible" as having to do with people who have some sort of disability. Merriam-Webster's definition has this as it's fifth definition. What I'm interested in are the second and third definitions:
[2] capable of being used or seen
[3] capable of being understood or appreciated
The system—which is the software, people who write it, people who manage them, and even future people who may be a part of it—is more accessible if more people can "use or see" it, or who are "capable of [understanding] it".
Highly compact code, or code using highly abstracted constructs (like a Domain Specific Language/DSL) is going to be less accessible than code that uses fewer constructs, or whose constructs are simpler.
In our example above, an inexperienced PHP programmer will likely get up to speed quickly in our for
-loop filled JavaScript, but have a bigger learning curve in a codebase that uses all of Ruby's powers.
Is this analysis too simplistic?
You may be thinking that while writing code with fewer constructs and using simpler terms could be make the system more accessible, it may eventually harm the system because of the decrease in velocity. Highfalutin concepts do serve a purpose and can leverage experience to allow an experienced team to go faster, right?
And, we've all probably seen what happens when a codebase fails to properly use abstractions. It becomes a hard-to-follow mess of lower-level bits of code intermixed with higher-level logic.
You probably think that more concepts, more abstractions, and richer tools will make the experienced engineers more productive, however they will make the inexperienced less productive:
This may be true, but the coefficients of this graph aren't clear. It could be like so:
While you could try to choose abstractions or tools that have favorable coefficients, thinking at a system level could lead to another option: remove inexperience (not "the inexperienced" per se) from the equation.
For many years at Stitch Fix, we required candidates to have some know with Rails. We assessed this via the often-dreaded take-home project. We did our best to make it something that gave us a good signal with minimal commitment for the candidate. But did this serve the system?
It certainly resulted in hiring experienced Rails developers. But, what if we assessed instead for general programming ability and then taught each new candidate what they needed to know about Rails?
Rather than assume new engineers learn the system through osmosis—and solve the experience gap up front—we could give those engineers intentional training to give them the experience they need:
In other words, a system that uses highly abstracted code can be accessible if that system includes a way to train incoming developers to first understand it, by removing inexperience from the equation.
Think about it instead as a funnel. By requiring incoming engineers to be experienced in all the abstractions you are using, the funnel is inefficient:
If you introduce training—the removal of inexperience—the funnel works better:
Again, the coefficients here are some fluid, but what this shows you is that there's a somewhat direct relationship between how you write your code and how you can manage the team and, thus, the overall system.
This approach can work for more than just hiring. What if a team wishes to introduce a new tool or library? What if it truly is the best tool for the job?
The effort to introduce this tool can simply add it to the portfolio, which would instantly reduce the accessibility of the overall system. Now, only a few developers can work on this new subsystem.
Or, the effort could bake in tools and training needed to bring along the rest of the team. Twitter famously created Scala School as a way to get their Rails developers to be effective with Scala-based microservices.
When considered this way, a new tool that may seem great can suddenly not look so attractive if the team has to be brought along to effectively use it. The team, its deadlines, its abilities, and its requirements are part of technology selection—how can they not be?
Think about your system. Who can be a part of it? Who is excluded based on how it works? Remember: the system is how it works, not how it was intended to work.
What changes could you make to include more people? What would have to be in place to add a new piece of tech or bring on a new programmer? What's the fastest way to remove inexperience?
It's hard to keep up with everything, and sometimes I learn something that has been common knowledge for a while. Maybe this happens to you.
In working on my book about dev environments, I purchased a very cheap Windows laptop. It is the first time I've used Windows since Windows XP (released around 2001 :). While the onboarding and setup wasn't nearly as painful as I was expecting, I ultimately wanted to install the Windows Subsystem for Linux.
The instructions were as follows:
wsl --install
No way that was going to work. That's just not how Microsoft is, right? Wrong.
It worked flawlessly. After a not-as-long-as-I-thought amount of time, I rebooted and opened up a terminal and there it was: Linux running inside a Windows box, officially sanctioned by Microsoft. Had to see it to believe it.
Unless otherwise noted, my emails were written entirely by me without any assistance from a generative AI.