There are lots of posts on the MATLAB newsgroup asking questions like why do I get this error "xxx"? One of the most common answers shows users how to track down the information for themselves.
Give a man a fish and you feed him for a day. Teach a man to fish and you feed him for a lifetime.
This entails learning to use the MATLAB debugger.
There are probably a lot of ways to debug programs. I am going to mention the ones that come to me quickly. These include:
- editing the code and removing semicolons or adding a keyboard statement at judicious locations
- setting a breakpoint at a particular line and stepping through code from there
- using a variant of setting a breakpoint by using dbstop if error
- seeing if the mlint code analyser can help (also reachable from the Tools menu)
- comparing variants of the code using the File and Folder Comparisons tool or visdiff for command-line access
Now that MATLAB has a great debugger (whether you use the commands or use the tools provided in the MATLAB Editor), I now longer use the technique where I edit my code. It's too easy to not remember all the spots I edited and have them revert, except for my bug fix, when I'm done.
If I'm not sure where the bug is, guessing where to place a breakpoint sometimes doesn't always work well. In those cases, I generally set dbstop if error> and see where things have gone wrong. From there, I can backtrack as needed, sometimes using the technique of setting a breakpoint at the top of the function which elicited the error. I can then run again and step through from the top. Sometimes I have to backtrack to other functions on the call stack, but at least I have a point of reference by then.
Since I often work from the editor, rather than the command-line, I automatically see the code analysis messages. I do find it useful sometimes to do a side-by-side comparison of two versions of a file and I generally do so from the editor Tools menu.
We've all heard the adage "a picture's worth a thousand words." With that in mind, instead of just looking at the code or variable values, I frequently find myself plotting interesting variables to get a quick look. Often I can tell from a quick plot exactly which variable is incorrect and that then helps me narrow down where to look for the bug.
Do you have some helpful debugging techniques to share? I'd love to hear them here.
Get the MATLAB code
Published with MATLAB® 7.10
Comments are closed.
11 CommentsOldest to Newest
The debugger certainly helps a lot to find the errors I’ve made. Especially dbstop on error combined with dbup to get up to my function at the point of error, where I can inspect the command that caused the error.
Editing the code to test things can be useful at times, but because I often forget that I made a change, I usually add a warning() call at the point of change so I don’t forget to change it back when I’m done.
A higher level strategy (can be combined with debugging functionality): split code up into small functions that do simple interpretable things. Then write and try tiny test cases for the functions before trying to debug big complicated runs of the whole program.
As soon as I discovered “dbstop if error” I put that into my startup.m file. I frequently run long simulations that have the potential to error at many places, because I’m trying a new scenario, or a new method, or whatever. When an error ONLY happens on the 300th run through some loop, and I’m running the program in the background, an error can eliminate hours worth of calculations if I’m not careful. “dbstop if error” is a great way to prevent that.
One problem I have with it though: whenever I give incorrect arguments to a built-in function from the command line, dbstop catches it and opens up that file in the editor. That isn’t helpful at all for me, for built-in functions – all I need is to see a few lines describing the call syntax. I don’t know how realistic this is, but I think it would be great if there were a little more control over the exact behavior of “dbstop if error”, on a per-file or per-directory basis.
I recently had a really nasty bug in the code. I needed to prune a tree that was had multiply connected nodes and some branches formed infinite loops. I chose a recursive tree search method with a counter to get rid of infinite loops. The tree was very large, so I wanted to pass the value by reference. Since that is not supported (at least without the properties class), I ended up using inline functions. It turns out that inline functions treated recursive function variables as being shared between each other. Debugging a recursive call with 30-50 functions on stack was a bear. Any tricks you can recommend?
You mentioned the breakpoints, but I just wanted to point out they can be conditional. I used to insert code that would conditionally call the keyboard command, but have found the conditional breakpoints much easier (especially when the file is read-only). Lifesaver when I need to stop in the 500th iteration of a loop.
I’ve also found I use the GUI to turn on stop if error and sometimes stop if error in try…catch. Not sure which dbstop if error is equivalent to. Stop if error in try…catch can be important, but it can also be frustrating as I realize how many of my and the MathWorks functions regularly throw and catch errors.
I am with Iain (2) on this one. Unit testing is a great methodology.
1) An absolutely must in my code are numerous assert’s, in the non-obvious cases augmented by short meaningful descriptions:
assert(strcmpi(str1, str2), 'Brothers cannot have different surnames')
2) Another practice I use to prevent possible errors are “if-elseif-else-error” codelets. Often we expect, due to what we think we are writing, to have one of two alternatives in the run time. However, due to what we have actually written, we may have more. The “else” statement is a trap in such cases, leading a program to a completely unexpected result. I therefore write:
if strcmpi(str, 'silica') ... elseif strcmpi(str, 'air') ... else error('Something''s wrong with material specification') end
3) On the “Ancillary Technique”: During the last couple of years I’ve been working on a big code with lots of complex calculations. The errors caught by MATLAB are the simplest ones there. My nightmare are those in the algorithms. To cope with them, I have introduced a so called infomode regime in my program. If set in the main file, it is then transferred to all the called functions. Those then give much more output and plot a lot. In the code it looks like:
if infomode oldFigure = get(0,'CurrentFigure'); localFigure = figure; end ... if infomode sprintf('%s: phase-matching points found: %g\n', mfilename, numel(pmp)); end ... if infomode set(0,'CurrentFigure',localFigure); hold on; plot(argnew, nnew, '.r'); try delete(circleHandle); catch; end; %#ok circleHandle = plot(argnew, nnew, 'ko', 'MarkerSize', 10); drawnow; end; ... if infomode close(localFigure); try figure(oldFigure); catch; end; %#ok end;
This helps a lot both in the free-run and in the step-by-step debugging.
Thanks for all the good comments. Unit tests are a wonderful technique for keeping code in check. I find that tests for uses in applications is also important in conjunction.
Bob mentioned conditional breakpoints and I think you might find these helpful for debugging recursive calls, especially if you have a way of figuring out how deeply nested you are and which iteration you want to stop at. That way you wait to stop many layers into the recursion.
I’ve learned that the approach to debugging starts with a sound design for the code. This includes writing down requirements including algorithms. Then, I write prototype code following standard style guides. Some of the most important guidelines include limiting the number of active lines of code within a single function to 80, generous commenting, standardized indenting including indenting of the text of comments & standard variable naming techniques. I also try very hard to understand the required syntax for existing functions that are used before I write the prototype code.
Frequently I find that I write functions only to divide the problem into smaller pieces and as such, the data flows almost linearly thru my code. This allows me to use identical variable names inside & outside a function.
Then, each function is tested independently as best possible. Further, I produce a Code Metrics report for each function & I work to get a silent MLint report.
I generally avoid nested functions & objects since I find they obscure the data flow. Still, when debugging is required, I first use the standard MatLab debugger setting breakpoints at appropriate locations. But frequently, I still need to write in temporary portions of code to further aid in troubleshooting.
At its basic level, troubleshooting requires that I contrive a theory about the cause of the problem & collect data to test the theory. When a problem is identified, a solution must be designed, implemented & validated. Debugging is complex & variable. Thus, it is difficult to give a specific answer your open ended question.
Kotya’s comments on using the assert function seem an attractive option.
Hear, hear for starting with sound design! With that, you can design in testability as well, making it more readily debuggable.
I’ve believe I’ve mentioned it in comments here before, but my biggest debugging frustration comes from the incompatibility of breakpoints and cell mode (i.e. breakpoints are ignored when running individual cells). I find cell mode an invaluable help in my research, where I’m setting up, running, and analyzing the output of large numbers of model runs; it helps to have lots of little “functions” in one file that I can run independently in different orders, but where I can keep all the variables in the workspace for command-line analysis as well. Debugging these cells often involves me peppering my code with temporary return statements as an alternative to breakpoints.
The inability to use breakpoints in cell mode is particularly aggravating when combined with the fact that error messages don’t indicate the line number where they occurred.