I am a puts debuggerer
tenderlovemaking.com
tenderlovemaking.com
I don't get the people who insist on using only one thing, and claim that their thing is the best. There's lots of tools and all of them have different optimal use cases.
a) For a while now, I've experimented with editor macros to comment/uncomment code that insert a special marker after the comment leader (like say //? for C++ or Java) that allows me to programmatically distinguish real comments from commented-out code. An immediate benefit is that I can select large ranges of text and enable/disable prints without messing up interspersed prose. A more subtle benefit is that it lets me leave prints in version control for weeks of time and watch which prints I use most often when debugging. I've also experimented with a couple of subsidiary experiments: a1) highlighting //? comments differently (much less saliently) than real comments, so they don't bother me as much in my editor, a2) having my comment macro set/increment a per-line counter which provides even more fine-grained telemetry about how my codebase is used. It's illuminating to save all the prints you made while debugging a problem that ends up being just a one-line fix. Even if you end up deleting all the prints immediately in the next commit. And if you think it makes merging harder, well that's just a task for another simple unix-like tool: to filter out commented-code before merging, and add it back in afterward.
b) A big problem if you do a lot of printing is to find the right prints that help you debug your problem now. Print too much and it becomes easy to get lost in all the verbiage. I've tried battling this problem with a 'zooming editor' which reads a potentially huge log but displays only stuff at the highest level of abstraction, letting the user selectively click on lines to expand more and more detail around them. I can even bring up this zooming editor automatically when my program dies in debug mode: http://akkartik.github.io/mu/html/090trace_browser.cc.html [1] This is an incredibly useful tool, because it turns a two-sided problem into a one-sided one; instead of having to walk the tightrope between too much logging and too little I can just log everything and poke at it in my leisure.
Not all these ideas are good. I gave up on a2) above after a few months, for example. But there's lots of room for questioning conventional wisdom and trying new things.
[1] Details on my literate format: http://akkartik.name/post/wart-layers
very much related to how much is too much. If you can simply filter out lines you don't want to see, like `grep -v '^first comment'`
(C++) One of my favorite middle grounds between console debugging and loading up the debugger is to output the actual stack walkback as a string of hex addresses in production code (on a conditional log level). This is relatively very cheap and avoids the entire cost of loading up the machinery to resolve the addresses to symbols in the critical path. It is assumed that you know what binary is running where and that you associate the stack walkback with the unique binary and thus can resolve the symbols out of band at a later date. (.. or have some handy automation that is notified and does it for you out-of-band and puts the results back into the central logging facility :))
At Bloomberg we do this using part of our infrastructure libraries and have wrapped up the logging-the-stack-as-hex part into a simple drop-in call: https://bloomberg.github.io/bde/group__balst__stacktraceutil...
All of the conditional logging machinery is pretty important as well, as it lets you maintain all of your detailed logging statements with the flexibility to configure however you wish at runtime in a production build while debugging. There's many different solutions for this in the C++ world, but ours is here: https://bloomberg.github.io/bde/group__ball__log.html The rest of the components are all in that package: https://bloomberg.github.io/bde/group__ball.html
Telling the difference between hot and cold and whether it was getting warmer or colder told me what I needed to know.
Saying as someone who once debugged something by ear making the motherboard emit a cathode ray tube like hum instead of blinking or beeping.
Conversely, the less state I have (and, typically, the more functional my code), the less useful debugging is. It becomes easier to see my bugs up front (as they have more to do with explicit logic, that is expressed directly in my code), and to trace through in my head what must be going on, and all I need to do is check to confirm my understanding, and that my fix addresses it correctly, which can often be done more easily by just running the code twice (with a puts/print statement), than attaching a debugger twice.
When I'm working on functional codebases (our JavaScript is done in a very functional style, and I personally write Clojurescript and Wisp for my personal projects), I find printing the result of function calls completely sufficient, though I suppose one can consider a REPL not that conceptually different from an interactive debugger: in fact, Codebug and I think Komodo's XDebug client actually give you a REPL to play with, set to the context of the given breakpoint
At $previousjob we had a guy who literally spent a year adding printf() statements everywhere to production code in order to help himself understand the existing code base he had to work on.
"It's not printf()-debugging, I'm doing extensive permanent instrumentation".
He even developed a whole framework for printf()-based debugging, to print messages in an artistic way.
The actual code became very hard to read due to the omnipresence of debugging cruft.
He also made the whole test suite dependent on the output of the debugging printf() debugging statements, making any kind of refactoring impossible.
I showed him how to use gdb and bought him a hard copy of the dtrace book, but it didn't help.
More details: http://akkartik.name/post/tracing-tests
I've found that if I can't understand the code it is because it is unnecessarily complex or, to mimic an above poster, there's too much state going on. Refactor to remove confusion.
Imagine yourself as a newbie asked to support such thing in production. Could you do it? If no why not?
1) writing your own code is a valuable learning experience
2) wasting time is a drag on the team and on the company so he should compromise between learning and producing (but where was management?)
3) too much logging pollutes the code too
void Error(string message,
[CallerMemberName] string callingMethod = null,
[CallerFilePath] string filePath = null,
[CallerLineNumber] int lineNumber = 0);
You then simply call `Error("something wrong with the frobnicator")` and the source line you need to come back to is already there. The only caveat is that this won't work in conjunction with variadic formatting for the message (`params object[]` arguments can't coexist with default arguments). `string.Format` feels like a small price to pay.At least, that's how it works when I'm developing under Linux. I'm not sure about Windows or Mac OS.
I get that there are definitely times when a legitimate debugger would come in handy, but I've never gone wrong with "puts debugging."
There is not much to learn if you have used any debugger in the past.
For example:
- When debugging multi-threaded code, you can test interlace events by manually pausing executing of certain threads at certain points. I've used this many times to manually reproduce a rare race condition.
- Tweaking a conditional breakpoint at runtime can be easier than rewriting code and re-running.
- Visual Studio has "tracepoints" which are like inserting a puts/printf statement in the code, but allows you to output things you might not easily want to write code to do like the current callstack or function name, e.g. "Print the callstack when I enter this function every 100 times I enter it"
- Poking around in memory can reveal things you wouldn't have thought to output, or is invisible in output, e.g. discovering a BOM in one string and not the other and having that be the cause of a comparison failure or a different path through the code than you think it should be taking.
- Trying to understand control flow when exceptions might be thrown or assertions might be triggered just isn't as easy with puts/print. You're likely to make a bad assumption. Understanding exceptions and how to make your application easy to debug is definitely a skill that begins at coding, not opening the debugger.
If you don't think you "have to learn a debugger" you're missing out on a lot of opportunities to be better at finding bugs.
GDB is not innate.
Therefore GDB has to be learnt.
---
I don't understand your lack of understanding :)
EDIT:
Addendum: Symbolic debuggers like GDB are non-trivial to pick up, is that what you're arguing against?
Are we supposed to believe that some people can understand unix, c++ etc but cannot learn/keep a cheatsheet handy so they can employ the ~10 commands needed to use gdb fruitfully?
I'm a pprint, fmt.Println, panic, console.debug, log.Print debugger !!
Godebug doesn't work with 1.5 vendor-ed deps, sigh !
You can get a much longer way with what Ruby lets you do with puts than you would with printf in C/C++.
Like the example with `method(:render).source_location` from the article. You can't do that in C/C++ on a general base.
Or pretty printing a struct is cumbersome. In Ruby you just do `puts obj.inspect` and get a readable representation of the object.
Yes, there's no default pretty print for structs, but no sane C programmer would ever expect or want that blubber to come available by default. Mainly because there's very rarely a need to printf-dump anything more than a select variable or a struct field. Different language - different debugging realities.
Especially __FUNCTION__ is by no means an equivalent to source_location.
__FUNCTION__ tells you, in what function you currently are.
source_location tells you "in what file has this method of this object been defined". Because of duck typing this is much harder to answer in Ruby. But even in C, because of linking it can be non obvious what function actually gets called.
https://github.com/vmorgulys/sandbox/blob/master/stackcity/r...
Sometimes but rarely, I use Nemiver:
If you are doing it because you are deep in some nested conditionals... you need to refactor.
Especially in multi-threaded code, it can be the only way - hitting a breakpoint and stepping through causes other threads to time out in unnatural ways, and you're hosed tracking down things that wouldn't actually happen. It's not uncommon that I've run into issues where attaching a debugger or a profiler gets its hooks into something in the depths of whatever COM library I'm using that breaks it anyway (I love RedGate's performance profiler, but I can't use it for some things on Windows 10, because of some security something or other, while it works on 7 & 8...)
Log4net is, without a doubt, my favorite dependency. ColoredConsoleAppender FTW. Even better, if I take the slightest care with my log levels in code, I can tune the log output up and down just by tweaking the config. So I can leave full diagnostics in a release, tune things down to hide the verbosity, with minimal performance hit, and dial it back up to debug levels if I encounter an issue in production.
And "tender lovemaking" for the title of a tech blog? That's childish and short sighted.
Rails has vibrant problems in its ecosystem (everything is global, tracing behavior is hard, everything is magic, many Rails developers have no idea how what they're using works), and this is both a product and a cause of that.
He doesn't "dismiss the use of a runtime debugger", he says "I don’t say this to disparage people that use a Real Debugger. I think Real Debuggers are great, I’ve just never taken the time to learn one well."