This post is part six of our Debugging Series 2021!
You’re faced with a scary error message in your console. You know you should Google it, but how do you do that effectively?
When we were interviewing people for this series, a common theme was approaching debugging like a mystery to be solved, or an adventure to overcome.
I’ve always been a huge fan of the Indiana Jones movie franchise (the first three, not that terrible fourth Kingdom of the Let’s Not Talk About It).
I saw a lot of parallels with my favorite archaeological adventurer and thought, “Let’s go deep on this!”.
Pretty much every day, some version of this happens to me:
I’m doing what I do, writing code, trying things out, when suddenly, my app crashes or throws some giant error message in my face. Having faced this over and over again (and come out alive) makes the current issue a little easier to accept, but I also feel a powerful sense of dread that this error message, this is the one that’s going to do me in.
If this happens to you and you’re trying to improve your archeological code adventuring skills, then read on!
I’m going to help you in the following ways:
Not sure how to pull apart complex errors? Do you paste in entire error message strings and get frustrated that the results aren’t what you want? I’ll show you how to pull out the key parts of error messages to make your searches more focused.
Do your searches turn up too few results, too many results, or not from sites you want? I’ll demonstrate some tips and tricks that you can use to make your search engine work for you.
Do you get overwhelmed with the solutions you find and have a hard time picking out how to proceed? I’ll share some rules-of-thumb and approaches to choosing the best solution for you, as well as some safety tips!
If you managed to find this post because you are trying to find an actual error message that I used as an example, I apologize for the detour. But stick around! My suggestions might help you find an answer, and, besides, it’ll be a nice distraction from your woes. 😃
AN ERROR MESSAGE HAS APPEARED AND IS COMPLETELY BLOCKING YOUR WAY
Asps. Very dangerous. You go first. Sallah, speaking to Indiana Jones as they overlook a snake pit.
The easiest way to avoid the traps in the secret chamber is not to enter the chamber at all!
On seeing a giant error message appear, you may be tempted to do something like comment out swaths of code, rip out a change you just made, or become lost in a maze of online suggestions and posts about problems that aren’t the one you’re facing.
Take a breath. Read the error message. Read it again. You don’t need to jump to the internet and start frantic copypasting; the error might tell you exactly where the problem is and even how to solve it:
'UIControlState' has been renamed to 'UIControl.State' Replace 'UIControlState' with 'UIControl.State'
Initialization of immutable value 'session' was never used; consider replacing with assignment to '_' or removing it Replace 'let session' with '_'
An error message – especially in certain languages or one from your code editor – can be very clear in what’s wrong. It may even tell you what to do if you read the entire message. Make sure you always read the whole thing (patiently) before jumping to the next step.
Ok, so what if the error message doesn’t tell you how to fix it? What do you do with it?
Balloq’s medallion only had writing on one side…you sure about that? Positive. Balloq’s staff is too long. They’re digging in the wrong place! Indiana Jones and Sallah talking to each other about how their rival Balloq missed some critical clues.
Error messages are often a wall of text. Very seldom do you want to use the entire message in your search. How do you pick it apart so you don’t end up reading posts about a different problem?
Here’s a quick summary of the ways I approach error messages. I’ll dig deeper into each one below.
- Use part or all of the human-readable part of the error message
- Remove hex values that look like memory addresses, but include hex values that look like error identifiers
- Remove words that are names of things that are part of your domain
- Include words that are specific to your software stack
- Remove words that effectively just say that an error has happened
This is not an ordered list of things to do, and many times, I might only use one or two of the above approaches. If I can trim an error message down to a handful of words, I’ll often start my search there and see what turns up. But if I’m not finding what I’m looking for, I’ll use more of the above methods.
The simplest approach is to use the human-readable part of the error message, the one that most closely approaches being understandable.
Most error messages are written by humans. Humans don’t think, “Oh, in this part of my code, I’ll raise error
0xGLHFWTG.” They might tag the error with such a code to categorize it, but they’ll also usually explain what it means and why it’s being raised:
Error 0xGLHFWTG: Could not clone certificates repo. Please make sure you have read access.
Just using the readable part of an error in a search instead of the entire message will generally improve the effectiveness of your searches. But there’s more to learn! Let’s continue.
Remove things that look like giant hex strings if they seem to be memory addresses or one-time values, but include hex values that uniquely identify the error.
Memory addresses and other one-time values, usually expressed as hex strings, are exclusive to your experience and not common to everyone having this issue. Including them would confuse your search.
But when a hex string is right next to the word
Error, it suggests that it’s a specific code that identifies the problem. This is similar to how certain errors are categorized by numbers, like
Error 404: Not Found.
Let’s take this example:
Error 0x8000FFFF: Module 0f07618b-5430-4291-bfd7-715bd9f3356f stopped due to catastrophic failure in thoughtbotBlogEngine at 0xf31f10 0xf64c48 0xf643cc. Session aa09baf8-e552-47bb-8828-77a71d52435a
If I saw this, here’s what I’d think: “
Error 0x8000FFFF, that sounds like a specific error code. I’ll include that.
Module 0f07618b-5430-4291-bfd7-715bd9f3356f…that could be a specific thing. I’m not sure. I’ll leave it out for the moment but I might bring it in if I’m not having success.
at 0xf31f10 0xf64c48 0xf643cc seems like memory to me, because it’s a bunch of values and the word
at makes me think that, too.
Session aa09baf8-e552-47bb-8828-77a71d52435a is probably a temporary identifier because session sounds temporary.”
There’s a really easy way to test your instincts: run your code a few times and compare the error messages! Any hex values that change from run to run should be excluded from a search, and any hex values that stay the same are probably identifiers that may help find your specific problem.
Exclude any words that are specific to your code, like the names of your classes, functions, and properties.
In the above example, if the object
thoughtbotBlogEngine was something that I had created, I would exclude it from my search because it would potentially lead to false matches that did not apply to me.
Here’s another one:
Value of type 'List<Never, ForEach<FetchedResults<HeartRateData>, FetchedResults<HeartRateData>.Element, HStack<Text>>>' (aka 'List<Never, ForEach<FetchedResults<HeartRateData>, HeartRateData, HStack<Text>>>') has no member 'onDelete'
In this message, I see my own class name,
HeartRateData. So I would at least trim the error message down to something like
Value of type 'List<Never, ForEach<FetchedResults<>, FetchedResults<>.Element, HStack<Text>>>' (aka 'List<Never, ForEach<FetchedResults<>, HStack<Text>>>') has no member 'onDelete'
Often these are combinations of individual words, like
Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from https://rubygems.org/ due to underlying error <Errno::EHOSTUNREACH: Failed to open TCP connection to rubygems.org:443 (No route to host - connect(2) for "rubygems.org" port 443) (https://rubygems.org/specs.4.8.gz)>
Here, the words I would pick out for a search are
EHOSTUNREACH. They look like they describe specific error conditions. If I just searched for that, that would be way too broad, though. So I’d also pull in part of the human-readable text to end up with a search like
HTTPError Could not fetch specs from https://rubygems.org due to underlying error Errno::EHOSTUNREACH
Many error messages have common phrases that are attached to multiple different errors. These phrases usually come from the compiler, the IDE, the runtime, the operating system, etc.
Here are some examples:
terminating with uncaught exception of type NSException Fatal error: Uncaught Error First throw call stack Runtime error in Exception in thread "main"
Including them may result in a lot of search results for problems that are not at all related to yours, because the same generic text is included in all of them.
That’s it for pulling apart error messages to craft your initial search. In the next section, we’re going to augment the search with helper words and symbols to help us in our quest.
Traps? Oh, yes. But I found the clues that will safely take us through them in the Chronicles of St. Anselm! Indiana Jones asking his father about his research into a dangerous crypt.
Ok. You’ve taken the gigantic baffling error message, you’ve broken it down to its fundamental parts, and you’ve pasted it into the search engine of your choice.
Sometimes, that’s enough. The first hit is recent, the explanation on the site is easy to process, you understand the proposed fix, and it gets rid of the error message! 🎉
The rest of the time, though, you get page after page of results that don’t apply to your error, are outdated, or are otherwise not helpful. Don’t despair! There are a series of tricks you can use to try to find the treasure you seek.
I’m going to use Google in some of these examples, but you can find similar tips online for Bing or DuckDuckGo or other search engines. You can also check out more advanced Google search tips than what I cover here.
Ways to get better, fewer, or more results:
- Add “tech stack helper terms” that are not in the error message itself to identify the technologies you’re using and sometimes version numbers to narrow the search engine’s focus.
You typically add these to the beginning of the search, before the error message. Examples:
swiftui ios 12 [the parsed error message] rails redis [the parsed error message] windows server ... java jwt ... bluetooth ble ... amazon s3 bucket ...
Add other “helper” words to your search to indicate that you’re researching a technical issue:
Use quotes. Or don’t.
To ensure that a specific error phrase is in the results verbatim (and not scattered pieces of it) put it in quotes:
"Unable to transition to the desired state".
If a search with quotes doesn’t have enough results, try searching for that phrase without the quotes.
The rule of quotes (I made up this rule) is to add quotes if you’re trying to reduce the number of total hits or increase the specificity of your search, and remove quotes if you’re trying to increase total hits because you’re not finding what you need.
Use a plus sign in front of a word to require it:
Use a minus sign in front of a word to exclude it:
adobe -photoshop -illustrator
site:tag to limit your results to a particular site:
-site:tag to exclude a particular site:
Some sites are question aggregators. If you follow a link and all you see are a list of issues that don’t look at all like what you searched for, bail immediately and exclude them from your next search!
- Use a date specifier to exclude results that aren’t relevant to you. Maybe you need to limit the results to the last month if you suspect the issue is with a recent software release, or maybe you want to scope the results to the past if you’re working with older software:
- Finally, if you start typing individual words (instead of pasting them in all at once), many search engines will suggest searches for you. If you’re stuck, don’t type past them; look at what they say and try one if you think it might be helpful. Maybe someone else pieced together just the right words to open the secret door you’ve been looking for!
Which one is it? You must choose. But choose wisely. For as the true Grail will bring you life, a false one will take it from you. Walter Donovan, speaking to the knight who is guarding the Holy Grail.
You’re almost home! You’ve pieced together a search string or ten, searched a few times, and are seeing results that both describe your issue and suggest approaches to solving it.
This is really important: Before choosing a particular solution, do your best to understand why it works and what it does. If you just paste in some code and try to keep plugging along, you may choose a poor solution for your use case and you won’t increase your overall understanding of your toolset.
Even with some level of understanding of what the various options do, how do you choose which option to try first?
In general, I try to select fixes that are narrow in scope and that I understand fully. This way, I minimize the risk of introducing new bugs or unneeded complexity to the codebase. More specifically, I want the complexity of the solution to feel equivalent to the complexity of the issue.
Here’s how I approach choosing a solution among all the ones that I find.
If a proposed solution is straightforward, like a missing property or method call or needing to set one property before another, I am most likely going to try the first suggestion I see. In these cases, the simplest solution is the best thing to try because it’s quick, and if it doesn’t work, I haven’t invested much time.
If a proposed solution is more complex, such as needing to run a set of commands on the main thread instead of a background one, or creating a custom initializer that puts an object into the right state, or overriding some base-level default functionality, I will consider a handful of solutions. Here I’m looking for and favoring thoroughness (without going overboard), attention to detail, and simplicity of implementation.
If the issue has the potential to occur repeatedly in the codebase, is more complex or nuanced than other problems I’ve faced, or if I see the potential for a complex solution to solve my current issue but also provide many other benefits, that’s when I start to consider more involved solutions, such as fully-fleshed out classes or even third-party software approaches.
If I’m having trouble understanding the solutions that people have posted, or if I see that people are proposing wildly different solutions, that’s when I tend to start reading the long exchanges and back-and-forth conversations where people discuss the pros and cons of different approaches. Otherwise, I have found that I can get lost in the weeds.
I’ve also found hints in what people have said in giant threads that pointed me towards what was wrong on my system, even though the problem or solution wasn’t at all related to what they were talking about!
- If I start running into other issues or realizing that what I thought was going to be straightforward has become much more difficult than it looked, I’m always willing to stop what I’m doing, stash the changes in git, and try something else.
When you’re considering trying out a code example that you’re going to add to your codebase, the most likely worst thing that could happen is that you introduce a different bug into your system or that it simply doesn’t work.
The same is not true of commands you run in your terminal or shell.
Do not blindly copy/paste shell commands from the internet!
Before you try running a shell command, be really sure you know what you’re doing. Some commands might have been posted maliciously, with the intent of opening up a security hole on your system. Other commands may do so accidentally if the people posting them did so without understanding what those commands would do.
Even worse is trying one shell command, and then another, and then another. You can very quickly fall into a ~~Temple~~ Cycle of Doom where each command is putting your system into a less stable state, and it may not be easy or obvious how to recover from it.
Once you find the thing that helped you solve the problem, document your implementation! Especially if the reason for a block of code wouldn’t be obvious to another developer or future-you, explain why it’s there. Describe or include the error message. Add a copy of the URL that discusses the problem or provides the inspiration or the code you ended up using. You or someone else will be grateful you did.
And if you came across other people in your searching who had the same problem but no one was able to provide them a solution, it’s a wonderful thing to go back to those pages and post a message explaining how you solved it. You know that amazing feeling of relief when you find a solution that helps you? You can be the source of it for many others!
Indiana, let it go. Indiana’s father, as he is losing his grip on his son, who is dangling over an abyss while trying to reach the Grail.
Don’t give up…until you should.
In your development career, you will come across non-critical error messages that cannot be addressed, most likely because there’s an issue in third-party software that you can’t control, and for which there are no workarounds. These messages can be frustrating because they can make you feel like you’ve written something buggy even if you haven’t done so.
I’ve spent countless hours trying to get rid of an error message or warning because I quite simply didn’t want to see it anymore. And in general, I don’t think that’s a bad thing. Today’s warning might be tomorrow’s bug or crash. Also, if your application generates too many error messages and you train yourself to ignore them, you may very well miss actual problems that get lost in the noise.
Like I mentioned above, most error messages are written by humans. This is especially true of warnings. Some people and the platforms they write for are a bit overzealous in the warnings department. (I’m looking at you, npm, and also at you, UIKit.)
But, just like you might choose the “Don’t fix the bug” option when assessing an actual issue, sometimes you’ll choose to ignore an error message in your console or suppress it because you have higher priorities to address or it doesn’t apply to your current task.
If you’d like to look at a few more real-world examples along with how I approached them, I’ve compiled them all here.
And what did you find, Dad? Me? Illumination. Indiana, talking to his father about their quest for the Grail.
It can be emotionally overwhelming to be facing a development deadline and to not only run into a bug that slows you down, but an error message that stops you cold. If you find yourself in that situation, remember the guides of the adventuring archaeologist:
Slow down. Read the clue in detail before grabbing your bullwhip and heading into the unknown.
Figure out which parts of the clue are relevant, and which ones will lead you on a wild chase.
Know how to talk to the librarian, a.k.a. your search engine, when you’re searching for clues.
When you’re evaluating possible solutions, always choose one you understand. And it’s a good idea to start with the simplest one.
If you are trying to resolve a warning or error message that isn’t actually causing problems, and you’re feeling like you’re spending too much time on it, remember that you can let it go.
And most importantly, never leave behind your hat.
This post is part of our ongoing Debugging Series 2021 and couldn’t have been accomplished without the wonderful insights from interviews with the following people:
- Adam Sharp
- Eebs Kobeissi
- Eric Bailey
- John Schoeman
- Mike Burns
- Rick Gorman
- Sally Hall
- Sam Kapila
- Sarah Dawson
- Sean Doyle
Keep tuning in every week for more great debugging tips.