Rubinius backtraces just got flipped right-side up

Improving developer experience, one error at a time.

Brian Shirai
Meta Language
6 min readApr 7, 2019

--

It’s easy when everything is going well. It’s when things don’t go as expected that we often need all the help we can get to figure things out.

When an exception is raised in a language like Ruby, we’re often left with a jumble of text and not much else. There’s that feeling of complete confusion about what happened and what to do next that washes over you. This is about as pure an experience of “things not going as expected” as we get.

Many years ago, Wilson Bilkovich added color to the Rubinius backtraces using ANSI escape codes. The source line where the exception was raised was rendered in red, the Ruby code in the Rubinius core library in blue, and other code (presumably application code) was rendered in the foreground color. This was a bold attempt to quell some extent of the confusion associated with backtraces.

Later, Mx. Evan Phoenix improved rendering of the backtraces by right aligning the rendering of receiver + method name and keeping the source location from obstructing the ability to read the method name by pushing rightward a source location longer than the terminal width.

The result was a backtrace that rendered something like the following:

This rendering made it significantly easier to read the method names by surrounding them with whitespace and providing an almost consistent right edge for your eyes to track.

However, there are a couple critical problems with this approach. Color is a very difficult thing to use to improve readability because of how different people perceive color (potentially not at all) and how any choice of color may interact with a person’s terminal colors, something Rubinius has no control over.

Another problem is that splitting up the source location makes it nearly impossible to quickly copy-paste the location to open a file in an editor.

Finally, there is the issue of the order in which the backtrace lines are rendered. In a blog post titled, Be Mad: Stacktraces are All Upside Down,it is noted that many programmer minutes are wasted scrolling back up to read what the exception was and what the relevant frames are when a backtrace is rendered to a terminal and scrolls out of view.

That post really makes you question, “Why do we render backtraces like this?”

Maybe we’ll never know. But a useful question is, “How might we improve how we render backtraces?”

I’ve taken a crack at that and want to share the results with you. I’m not saying this is the best we can do (more on that later), but I’ll walk through the decisions I made and my rationale for them.

First, I took the upside-down criticism to heart and reversed the rendering order. I also rendered the source code location indented on a separate line when the total rendering length of the backtrace line would exceed the terminal width. This makes it possible to easily copy-paste the source location. Finally, I ditched the color and opted instead for bolding the receiver + method name component.

Here’s how that looked:

I was pretty happy with the result. The contrast makes it easy to read the method names. (If the terminal doesn’t render bold, it’s only the contrast that would be lost, not any content.) And the exception message at the bottom makes it a snap to read what happened and start looking at the context (the backtrace) by scanning up from where my cursor is now. If I need to copy a source location, that would be as simple as a double-click or Command+click to open the file (I use iTerm).

Something still bothered me a lot about this rendering. To figure out what, I broke the backtrace line down into components: 1. the fully-qualified receiver name (A::B::C); 2. the instance / module method indicator (#, .); 3 the method name; and 4. the source location.

I also considered the tension between rendering a single line of a backtrace (e.g. formatting decisions I would make in isolation considering only that line) and rendering a backtrace as a whole (more than just a set of individual lines).

The number one piece of information that I want to index on when trying to make sense of a backtrace is the method name.This tells me the most about what may be happening. A secondary piece of information is either the fully-qualified receiver name or the instance / module method indicator. Once I’ve keyed in on one of these, I may move to examining the source code at the specified location.

So, if the method name is so important, why is it buried in a dense thicket of characters in the middle of the backtrace line? Mostly simply because A::B::C#name or A::B::C.name is a well-established convention in Rubinius to refer to a method. This convention carries over without much question to rendering a backtrace.

I decided to challenge this convention and pulled the method name to the front of the line. I also added a bit of whitespace to separate the method name, instance / module method indicator, and the fully-qualified receiver name. I retained the bold formatting for the entire receiver + method name component.

Here’s what the final rendering looks like:

This rendering provides a right-aligned edge for my eyes to track while reading method names, as well as a fixed point to start reading the fully-qualified receiver name. In contrast to the earlier rendering, aligning this component doesn’t push some names way far to the right because of a high variability in the length of the receiver name.

The source locations that are rendered on a separate line are indented and left-aligned. This creates another line in the backtrace that supports scanning up the method names.

Another thing I noticed about reversing the order of rendering the backtrace lines: I can scan up the relevant portion of the stack, and then back down mentally following the execution of code, without scrolling my terminal. This gives me the best of both options, something not possible with the previous ordering once the relevant lines scrolled out of view.

Leading with the method name also improves readability when functions will be interleaved with object-oriented method names in the backtrace.

I think the new rendering is a big improvement in readability and developer experience (DX), but I’m sure there is more we can do. Terminals are still fairly primitive devices. The Atom Rubinius terminal and the Rubinius Console are two projects aimed at significantly improving developer interaction with running code without moving into the traditional IDE space. Finally, improving the modularity of backtrace rendering in Rubinius is an important aspect of making Rubinius better at being language agnostic.

So, go make something unexpected happen with your code and help us improve your developer experience.

--

--