5 mins

Building accessible engineering teams with accessible code.

Nihil de nobis, sine nobis – it’s a slogan that originated in political discourse, but entered the scene of disability rights in the 1990s. Translated, it might look more familiar: ‘Nothing about us without us’.

Diverse engineering teams build better products because they’re more familiar with different ways that their products might be used. When engineers think about accessibility, they think about tab order, alt-text, contrast, and variable inputs. But accessibility is all-too-frequently an afterthought.

At its core, accessibility is about removing barriers to access. Diversity includes disability, but disabled employees are rarely represented on these teams due to a lack of accessibility. This doesn’t just apply to the products that we build, but also to the processes that we use to build them.

How can we embrace ‘nothing about us without us’ as engineers? By hiring engineers with disabilities and thereby ensuring that products are designed and built with disabled users in mind. How do we set disabled engineers, like myself, up for success? By building accessibility into all aspects of development.

Developer tools have issues

If you ask a developer what their favorite editor is, you’ll likely receive a flowery ode, along with advice on what macros, plugins, extensions, and themes to use. The same holds true for other common development tools. Developers are nothing if not opinionated!

As I discovered when my vision started to decline, accessibility features on these development tools are less supported than they are on end-user products. My favorite editor didn’t work with screen readers – a real blow. The diagrams in documentation tend to have no (or useless) alt-text, captions, and get fuzzy when you zoom in on them.

What’s the commonality here? Code.

Meet developers where they are

Being a disabled developer involves roughly assembling a set of tools together, finding a way to integrate those tools into your team’s workflow, and navigating the code review system that your company uses. For me, I use a combination of contrast settings, magnification, text-to-speech, and screen readers – depending on the accessibility capabilities of the various development tools that I’m using. While building accessibility in from the start is the reality we want, we need to fill in the gaps we have. That starts with the code.

Making code accessible

Code is our lingua franca; our common language. Our stacks may change, as may our tools, but there will always be code to write, and code to parse. Whether you’re a new developer or a senior one, there are concrete steps we can take to make code itself more accessible. They’re simple, achievable, and they make code better. These are three of the lessons that I’ve learned over the past few years.

First, let’s take a look at the following Go code:

vecA, vecB, vecC := Vector{1, 2, 3}, Vector{4, 5, 6}, Vector{7, 8, 9}

vecA, vecB, _ = swapTwoVecs(vecA, vecB)

t, _ := addAllVecs([]Vector{vecA, vecB, vecC}...)

Here, we have three vectors defined. We use a swap function on two of them, and finally we add them all up to form a total. Three lines of code. This is perfectly functional code, but not very accessible. Let’s see if we can make it better.

Organization

For three lines of code, one might not think too deeply about the structure of the code. However, thinking about the accessibility of small sections of code leads to better structure overall. And organizationally, there’s a lot to be desired, even with these few lines. Let’s take it one line at a time.

The first line defines and initializes the three vectors that we’ll be using. However, the next line only uses two of them. Once we get to the third line, we may have lost track of all the variables we declared at the top of the function! Consider instead:

vecA, vecB := Vector{1, 2, 3}, Vector{4, 5, 6}

vecA, vecB, _ = swapTwoVecs(vecA, vecB)

vecC := Vector{7, 8, 9}

t, _ := addAllVecs([]Vector{vecA, vecB, vecC}...)

Functionally equivalent, and even a bit lengthier, but more readable. The best practices demonstrated in this adjustment are:

  • Declare variables, structs, interfaces close to where they are used
  • Preserve context in structure as much as possible
  • Group components together logically

By logically grouping components together, you have to keep less in your mental buffer, which is great if you use a screen reader, and you have to navigate around the code base less to find definitions or declarations, and that’s helpful if you have difficulties with fine motor control.

Naming

The saying goes, ‘there are only two hard things in Computer Science: cache invalidation and naming things’. We’ve used somewhat descriptive names, but there are the following issues with this particular approach:

  • Starting each variable with “vec” is redundant
  • Variable names are not pronounceable
  • Function names are overly complicated
  • ‘t’ isn’t very descriptive

Let’s tweak it:

a, b := Vector{1, 2, 3}, Vector{4, 5, 6}

a, b, _ = swap(a, b)

c := Vector{7, 8, 9}

total, _ := add([]Vector{a, b, c}...)

Here, we have adjusted our variable and function names to demonstrate these three best practices:

  • Use short, pronounceable names
  • Use meaningful variable names when appropriate
  • Don’t make up words

Short, pronounceable names work best with screen readers, which often can get confused with the concatenated words that are common in code. Using meaningful names, which we did by renaming ‘t’ to ‘total’, reduces the amount of context that a person needs to keep in their head about what the variable does. Those two recommendations aren’t at odds either – single-letter variable names can signal the intended lifespan of the variable, whereas using a whole word can indicate that the variable is going to come back into play later in the function.

Spacing

A controversial element in code is the newline. It is an essential part of making code accessible, and frankly, overall readability. It’s one final modification we can make to our code:

a, b := Vector{1, 2, 3}, Vector{4, 5, 6}

a, b, _ = swap(a, b)

c := Vector{7, 8, 9}

total, _ := add([]Vector{a, b, c}...)

Newlines are something we kind of pepper in our code without really thinking about it. However, they can be really powerful signals. This one additional line introduces a pause between two separate sections in the code. When adding newlines to code, follow these best practices:

  • Treat newlines like paragraph breaks; use them in moderation
  • Use them to guide the developer to blocks of functionality
  • Add newlines intentionally, not just because they look good

For developers using screen readers or refreshable Braille displays, a newline can indicate a place to add functionality or a good time to pause and think about the prior code block, before moving on to the next section.

Write code for everyone

At the end of the day, we’ve made some small tweaks to three lines of code, and wound up adding two additional lines. We didn’t change anything major, and yet we’ve made two pieces of functionality clearer, easier to navigate, and easier to discuss. There may be no linters (yet!) to help us with adhering to these best practices, but diligence and documentation go a long way.

While we should all be building accessibility into our products from the start, we should also be building accessible engineering teams. That starts with writing accessible code.