“I must be doing something wrong here, but I can’t tell how to properly do this from the docs. Help would be greatly appreciated.”
If you’ve been writing software for any length of time, you’re familiar with this problem. You’re chugging along, making progress, building the thing you want to or are supposed to build, when you hit a snag. You know what needs to be done, but aren’t quite sure how to do it. You try a few approaches based on your intuition, but none of them work. It’s not a problem, you think. I’ll just google it. A few StackOverflow and blog posts later, you’re still not any closer to solving your problem and you’re coming to a realization. I’ll have to read the docs. They’re going to have the answer!.
A lot of the time, once you read the docs, you do end up finding enough guiding information to help overcome the snag and move on to the next increment of work. But every now and then, even if the docs give you some additional context around the problem, you find that you are still in the dark when it comes to actually solving it.
This happens. Docs, especially software docs, are often works in progress. Even well documented projects can be sparsely documented at their edges. What are you supposed to do when they aren’t enough? Let’s take a look at five time-tested strategies and behaviors you can use when you reach a “docs dead-end” next time. There is no particular order to these. Sometimes, just one of them will help you get unstuck, and other times, you’ll have to use a combination of them to move forward.
While the docs for a gem may be incomplete or out of date or just plain wrong, you always (assuming you’re working with open source libraries) have access to the source code. This is one of the nice things about working with open source software - we can peer into the black box and discover its inner workings. Doing so has the potential to increase your understanding of the gem you’re using and help you get unstuck. When you’ve worked through a few class and method definitions, you’re often able to gain a clearer picture of how things are intended to work, and update your mental model accordingly.
There are a few ways to start reading the source code of the library you’re interested in learning more about.
The easiest way is if your IDE or editor supports code navigation for third party libraries. I recently started trying out RubyMine (a Ruby/Rails IDE) and one of its features I really like is the ability to hover over any method or constant in Ruby, command-click it, and be navigated to the exact place where the method or constant is defined. VSCode, which I also use, has a similar feature, where if you command-click a method or constant, you will be shown a link to the docs page, from where you can choose to view the source code in GitHub.
A low tech way to inspect third party source in Ruby is to use Bundler. Doing
EDITOR=<your editor. vim for e.g.> bundle open <gem name> in your command line will open up the library in your editor of choice and you will be able to peruse its code as easily as you would your own, with project wide search, command-click, etc.
A strong advantage of being able to read third party code in your editor or IDE is that it enables you to perform “active” source reading. This is when you actively insert breakpoints in the third party code (with
debugger, your IDE’s debugging system or even just
putsing the variable you’re interested in), and then peer around in the program’s state to gain a deeper understanding of what’s going on.
Another low tech way is to visit the GitHub (or other public repo) page for the library and use the search box.
There are some pitfalls to watch out for with this strategy, however. It is not always easy to extract the information you need from a large open-source codebase. The code style may be unfamiliar to you, assumptions may be invisible, or it might just be plain hard to understand code. Take your time and realize that while you may not understand everything in one go, your skills at reading source will improve over time. I also keep in mind that I don’t need to understand all of it; just enough to help me solve my problem.
Snags in programming can result from a mismatch between your mental model of how a system works, and how it actually works. These mismatches can range from the small (I thought this method takes in an array vs the method actually takes in a hash) to the systemic (Wait, I need to have Redis running to be able to broadcast Turbo streams?). As Rails developers, we rely on a large corpus of open source code to build systems, and as such, it is unreasonable to expect ourselves to have perfectly accurate models of the third party libraries our code depends on. This is why we rely on docs.
When docs aren’t enough however, experimentation offers us a way to continue making progress. A good “experimentation session” in my experience usually comprises the following:
Though coming up with alternate approaches when you’ve hit a snag is easier said than done, it is also an ability that you can develop over time. The more code you read, write and review, and the more you deepen your understanding of how large systems (databases, file systems, networks, browsers, etc) work, the better your brain will get at asking “what if I tried this?” questions.
After you try out an alternate approach, I’d recommend asking yourself “why did this work (or not work)?”. If you can, try to get to the bottom of things by reading the source code of the third party library involved. This is an important step because it sets you up nicely for future situations when you run into a similar problem. Even if you’re not able to actually solve the problem you currently have, you will still benefit from the effort you undertook to expand your mental models.
For a quick story on how experimentation helped me help a stranger on the internet, check out my article on turning off sharpening for ActiveStorage Variants.
A pitfall to watch out for with experimentation, especially if you’re the type that loves it, is that it is easy to spend way too much time in the weeds trying a million different things. For this, I can offer three recommendations. One, take detailed notes on what you try, what worked, what didn’t work, and what changed. This will help keep your thoughts organized and your experimentation focussed. Two, timebox yourself. And three, once your time is up, move on to one of the other strategies described here.
If you have the benefit of working in a team, the odds are pretty decent that one of your teammates will be able to offer you insight on the problem you face. Moreover, the very act of coming up with a question to ask them is a powerful way to solidify your understanding of the problem and its surrounding context. The more effort you spend on ensuring that your problem statement and question are as clear as possible, the easier it will be for your colleagues to help you.
If you work alone, you can play a classic game in computer programming known as rubber ducking, where you explain your problem to a rubber duck or your favorite inanimate object. I can’t speak to the neural mechanisms of how this works, but I have found that it is helpful in organizing my thoughts and helping me see my problems from a different perspective.
A logical extension of the “ask your colleagues” strategy is to ask the internet. Asking a question on StackOverflow, Reddit, Discord etc, are all decent ways to make progress on your problem. You’ll want to put some effort into crafting your question so that it is as easy as possible for someone to help you. That could mean providing enough supporting information, a “proof of concept” if you’re referring to a system which doesn’t “work”, or even just clearly worded instructions on how your problem can be reproduced. When you’re framing a question, I highly recommend opening your favorite text editor and typing your question in there first, with the goal being to first sculpt it into something readable, and then to post it online.
The drawback of this approach is that you may not get help in the timeframe that is most useful to you. However, even if you don’t get an answer back, you will likely still benefit from the effort you put in to clarify your thoughts and experiences into a question.
When you hit a snag, you may spend hours looking for a solution in the docs, by experimenting and by reading third party code. When these efforts are not successful, it pays to take a step back and re-assess what it is that you’re trying to accomplish. Can you re-frame your goal in a way that doesn’t require you to solve this particular issue? Are you complicating things needlessly?
Re-framing, when done successfully, allows you to move forward and deliver value in spite of the snag you’ve hit. While it is true that you’ll still not have solved the original issue you ran into, you can always go back to digging into the issue later if you find yourself curious about it.
Re-framing is useful to try collaboratively as well, whether you’re pairing or mobbing on some code or even having a higher level discussion about a feature or business requirement.
Being stuck with uncooperative code is frustrating. Even more so when there is no clear documentation on how you can solve your problem. The strategies I’ve written about above will hopefully show you a way out. More importantly, I hope the strategies above are able to help convert your frustration into curiosity and enjoyment.
If you got value out of this article and want to hear more from me, consider subscribing to my email newsletter to get notified whenever I publish a new article.