Debugging with a screen reader
Your program has crashed and the screen reader has just delivered six seconds of punctuation, filenames and indignation.
Good. You have evidence.
Debugging is not the art of staring at code until guilt causes the bug to reveal itself. It is the process of narrowing down what happened, where it happened and what assumption turned out to be wrong.
Do not start by reading everything
A traceback or compiler log may contain dozens or hundreds of lines. Most of them are context, not the first thing you need.
Start by looking for:
- The exception or error type.
- The error message.
- A filename.
- A line number.
- The first location in your own code involved in the failure.
Do not heroically read 700 lines from top to bottom. Search. Words such as error, exception, failed and the name of your source file are useful anchors.
Tracebacks: read from the useful end
Many Python tracebacks end with the exception type and message. Start there, then work backwards until you reach code you recognise.
Traceback (most recent call last):
File "example.py", line 18, in load_project
value = settings["missing"]
KeyError: 'missing'
The useful first facts are: this is a KeyError, the missing key is missing, and your code reached the problem at line 18 of example.py.
You can investigate the rest after you know what question you are asking.
Use screen-reader review instead of disturbing the program
If the error appeared in a terminal, use the techniques in Using the command line with a screen reader.
With NVDA, review the output independently of the live caret. With JAWS, use the JAWS Cursor or Virtualize Window. Copy the useful section into an editor if the terminal is becoming awkward.
Do not make yourself reread a hostile moving interface merely because that is where the error originally appeared.
Logs are evidence, not literature
Logs are usually meant to be searched.
If the application writes a log file, open it in an editor where your screen reader has predictable navigation. Search for the time the problem occurred, an error level such as ERROR or CRITICAL, or the feature you were using.
A useful log answers questions: what was the program trying to do, what went wrong and where? It should not answer a fourth question called "what was the user's password?" Keep secrets and tokens out of logs.
Reduce the problem
If opening one enormous project crashes the program, try a small project.
If importing ten files fails, try one.
If a function receives twelve arguments, test the suspicious value separately.
Every reduction removes possible causes. A five-line example that fails in the same way is much easier to reason about than the entire application.
Print debugging is not beneath you
A strategically placed print or log statement can answer useful questions:
print("selected path:", path)
print("items found:", len(items))
print("about to load settings")
Do not scatter 400 unlabeled values across the console. Label them so speech tells you what you are hearing.
Once the bug is understood, remove temporary noise or turn useful diagnostics into proper logging.
Breakpoints can be useful, but only if the debugger is usable
A graphical debugger can pause execution, inspect variables and step through code. That is powerful when its controls and variable views work well with your screen reader.
If your IDE's debugger turns variable inspection into an accessibility obstacle course, you are not morally required to use it. Logging, small reproductions, command-line debuggers and targeted tests are legitimate debugging tools.
Do not fix the first thing that makes the error disappear
Commenting out the crashing line may stop the crash. It may also stop the program doing its job.
Ask why the bad value reached that line. Was input invalid? Was a file missing? Did an earlier operation fail? Is the code assuming something that is not guaranteed?
The error location tells you where the problem became visible. The cause may be earlier.
Keep a useful bug report
If you need help from somebody else, give them something they can work with:
- What you were trying to do.
- What you expected.
- What actually happened.
- The smallest steps that reproduce it.
- The relevant error or traceback.
- The application and version involved.
"It doesn't work" is technically a bug report. It is merely one with a promising future in archaeology.
A practical debugging loop
- Reproduce the problem.
- Capture the useful error.
- Find the first relevant location in your code.
- Reduce the failing case.
- Inspect or log the values involved.
- Change one thing.
- Run the same test again.
- Once fixed, test nearby behaviour so the repair has not created a new problem.
Debugging becomes much less mysterious when you stop treating the error as a verdict and start treating it as data.
Continue learning
Using the command line with a screen reader covers reviewing live terminal output with NVDA and JAWS.
Screen-reader testing moves from debugging your own workflow to testing the accessibility of the software itself.
Return to the Developing with a screen reader learning path.
Image Description