Reify debugger semantics by making it possible to specify debugger behavior with swift code

(B Cohen) #1

There was an article recently that made it to hackernews frontpage where an experienced developer states a case against the use of a debugger -- https://lemire.me/blog/2016/06/21/i-do-not-use-a-debugger/. I disagree pretty strongly with the perspective espoused in that article as I see a large amount of value that comes from the use of the debugger but I do think debugger's are held back from being as useful as they could be by UI problems.

As I've been thinking about it -- I've become more and more convinced that UI issues holding debugger's back aren't so much problems with the UI of IDE's or with the precise interface of console-based debugger's -- but are actually problems with how and where the developer specifies what they want to observe via the capabilities that a debugger offers.

One example argument for print-based/logging based 'debugging' goes something like this:
If you add logging/print() to a program it often pays dividends into the future. You might solve the problem you are trying to solve now and in doing so you might also establish human-brain-focused messages that could help you or another developer understand program behavior in the future. If you had instead solved your problem by relying on debugger level tools (breakpoints, or debugger-integrated logpoints) you are not creating any useful future value.

I've been thinking about this argument for a bit and I think the argument supports development of a feature I've been dreaming about that I would really like to see added to a language ... I think it would be really powerful to integrate control logic for a debugger into a codebase. One could imagine doing this by sprinkling some kind of sharable IDE config into the code repository -- but there are a lot of reasons that this could never offer a great experience -- functionality tied to specific IDE and probably even IDE version, it would almost certainly be challenging to represent this functionality in a way that worked well with version control, opaque functionality baked into 'outside the codebase' quantities like this are unlikely to be considered worth 'maintaining' by most teams since the functionality described would be some bizarre 'other thing' that isn't exactly part of the program code, there can no such thing as compile time checking for any such functionality, refactoring wouldn't work into debugging code ... etc etc.

Imagine instead if something like a debug() block were added to the language itself allowing one to drive debugger behavior with text embedded directly into the relevant places of the source-code:

Imagine the SUPERPOWERS that could exist via a DEBUG block:

debug(...) {
   ...
}

Here are some vaguely sketched examples to try and suggest the concept:

debug(problemContext: AuthenticationIssues) {
    pauseDebugger()
}

a developer working on debugging some issue related to Authentication might sprinkle these blocks in various places throughout the code -- then when she finds and fixes the problem, she'll see the places she added these blocks when she reviews her code changes -- and she'll probably know right away which ones might be useful in the future and worth keeping for next time and making part of the commit. When she has to spelunk in that part of the code again -- she or another developer can ensure they are in AuthenticationIssues debugging context and they'll get have those breakpoints/logging outputs activated -- and there's a good chance they'll quickly recover the mental context needed to understand the pieces relevant to their new issue.

More elaborate uses are obviously possible:

debug(problemContext: AuthenticationIssues, runtimeCondition: someValue.currentState == .someState) {
   debugValue(someValue)
}

These are intended to be just very vague handwavy examples -- but I think adding a UI for the debugger to the language could:

  1. Encourage developer's to share/maintain useful debugger functionality in their codebase
  2. Offer value to codebases which adopt a 'debugger friendly' code-style (similar to the kinds of dividends that result when developers learn to write code to be 'testable')
  3. Produce well-defined semantics for debugger behavior -- this could encourage the existence of well-defined debugger protocol's -- for logging, for monitoring, ... - and possibly help expand the set of tool's capable of taking advantage of advanced runtime monitoring of program behavior ...

Does anybody else think this kind of functionality would be interesting?

(Jens Ayton) #2

I’ve also had thoughts about language and framework support for better debuggability, but from a slightly different perspective: making libraries more debuggable for their users, without diving into the library code. Swift’s CustomReflectable is a step in this direction.

One particular feature I’ve wished for multiple times is the ability for a library to customize how stack traces (and cross-thread traces) are shown in the debugger. Anyone who’s debugged an app using Boost or an Rx-style library can probably see the utility of this.

On the other hand, in an ideal world the same tools could be applied to crash reports. It’s easier to see how that could work if the tooling was written in a scripting language rather than integrated into the library.