So this idea just sort of crystallized in my head: what if rather than documenting code does in comments, we instead added a bunch of printfs to the code so, when necessary, the code can explain what its doing while doing it. Of course, this doesn't seem so new and novel, but what I'm suggesting is that comment syntax be replaced by printf syntax in a language design. Then, we somehow must provide a way to "turn it on/off" so that when it is off, it has zero cost, and we can selectively turn it on during debugging when we need code to explain themselves.

This should apply to library documentation also, so that methods can explain what they are doing when stepping through code (as Bret Victor demonstrates in his learnable programming essay), while the print statements should clean enough that they can also act as static documentation when necessary.

What do you all think?

Comment viewing options

logging

Isn't that just logging? For example, in C or C++ you can have a logging macro that expands to nothing in the release build, but conditionally prints something in the debug build. In Java, I think they use static final booleans to optimize out unneeded logging calls. Also, usually it's not just printf, you can configure different severity levels, different logging destinations...

Technically, ya. However,

Technically, ya. However, the idea is to replace all static comments with executable ones for the purpose of documenting code.

cons now, pros later

Since this may be long, in a separate comment I'll describe ideas agreeing with your sentiment (adding generated code that helps describe what happens). First my objections: some good comments are about things not currently happening in a piece of code, so there may be nothing you can print that conveys the relevant info. Let me back up and give some context.

I comment code heavily, but I work with several people who comment little or not at all. (One guy is determined to never write a comment; he says "the code is self explanatory" and when I ask simple questions about his code demonstrating this is false, it doesn't change his position. I figure he just wants to be less dispensable.) Some folk want to comment more, but they forget until I say something. In reviews I ask a question about what a bit of code does, and the reply is sometimes a brilliant analysis which you cannot figure out from reading the code. Then I say that explanation is so remarkably cogent and helpful to a future dev, it's a shame it doesn't appear in the code as a comment. A lightbulb goes on; they add it as a comment. Excellent ascii figures illustrating an explanation sometimes get drawn up, and I marvel they aren't a code comment already: relationships in space and time.

When I feel preachy I write a short guideline for good comments, going something like this. I want to know why; I can see what, but I want to know indirect effects you think something has later, especially if that effect is far away. Any time you arrange part of an invariant be true one place, where the rest occurs elsewhere, I want that invariant mentioned, especially if searching for it won't work because it's never mentioned (when just a notion in the dev's mind). If you incur an obligation locally that someone else must satisfy remotely, that should be noted, as well as resources you allocate but don't free because someone else must (as opposed to being a bug, the way it looks when read casually). When semantics are distributed over several locations in space and time, but have some unified pattern, pick some place to describe it, then just briefly refer to it in spots where you touch part of the business. Contractual obligations should be spelled out: caller must do this, while I the callee must do that (and caller can rely on it). The first sentence describing what a function does (as an interface gloss for callers) should summarize the general idea all in one shot. You can always expand detail in subsequent sentences. If it's an important detail ("never do xyz or you'll suffer") and a casual reader might easily miss it, write it down; you get extra points for saying it very succinctly, increasing odds it will get read.

Many of those things cannot reasonably be printed, because printing will display locally known info, but the most important issues are non-local parts: relationships affected elsewhere. If you chivy folks to replace comments with printing code, you'd encourage squelching of exactly the comments I want to see most. (Then in a future version of The Christmas Carol, I'd visit you as a ghost with a beef.)

I'm working on logging-based tests

http://akkartik.name/post/tracing-tests

Basic idea is to check for side-effects dumped to a back-channel rather than the direct return value. It looks like logging, but uses a more efficient in-memory data structure behind the scenes.

The benefit is that certain kinds of tests become much easier to write, almost easy enough that people might, you know, actually write them. For example, I can test that sorting 2N vs N elements requires no more than a factor of alpha swaps.

I've been trying to extend this idea for still more kinds of tests, for things like concurrency and fault tolerance. But I'm finding that doing this requires going down all the way to the substrate of the software stack. I have to reinvent the stack all the way down to the OS, to answer the question: what would OS's look like if we hadn't been taken in by the idea that "tests can only show the presence of bugs, not their absence"?

A little more meat on my current directions: http://akkartik.name/about

Phase mismatch

I'm supporting something vaguely similar to this, but I'm having trouble understanding the details of what you're proposing. What does the following do?

for i in [0..10]
-- The value of i is %d{i}.


Of course I do not speak for

Of course I do not speak for Sean, but I can't imagine that would do anything other than:

The value of i is 0.
The value of i is 1.
...
The value of i is 10. (or 9, depending on whether you ranges are inclusive or exclusive)

Am I missing something here?

Then I'd expect that you would be able to click on each of those lines to navigate to the execution context at the point when that line got executed, just like in his Usable Live Programming.

I don't think that's particularly useful

for i in [0..10]
-- The value of i is %d{i}.
-- Its square is %d{i*i}.


Does each comment have a box with its own scroll bar? That doesn't seem very helpful. Or, is there a log somewhere that interleaves all of these "comments"? If so, then this is just print logging, isn't it?

Check this out: Usable Live

Check this out: Usable Live Programming web essay, especially the 'Tracing' section. Essentially this would be an enhanced syntax for trace(...). So yes, I would expect it to be "just print logging", except with magic on top.

Perhaps some way to create nested sections is in order, so that you can expand/collapse sections. Perhaps with begin section and end section indicators, or maybe the collapsible sections could be created automatically based on the structure of the code (loops/function calls).

If you then add some way to log arbitrary visual & interactive objects and not just text, you have a great contender for the future of programming IMO. e.g.

-- The posterior distribution is %{plot(p)}


Probing vs. tracing

Yes, Sean's doing some neat stuff there. Using the terminology from that page you linked to, I can phrase my point as: comments should be probes and not traces. Sean, how does your system handle probing inside a repeat block?

If you then add some way to log arbitrary visual & interactive objects and not just text, you have a great contender for the future of programming IMO.

Yeah, this is something I'm doing, as well. I agree that this kind of feature will have a wow factor for many people that will probably help drive adoption and that this kind of thing will kill text-only languages. But I think it's necessary but not sufficient. There are other concerns that are just as (or, IMO, more) important in a language.

Here is the associated

Here is the associated paper: http://research.microsoft.com/pubs/189802/onward011-mcdirmid.pdf

Many code blocks have ambiguous executions contexts;
e.g. methods have multiple callers while loops have multiple
iterations. YinYang solves this problem by projecting an execution
context according to how we navigate to the code.
...
But I think it's necessary but not sufficient. There are other concerns that are just as (or, IMO, more) important in a language.

Definitely, this is one aspect of the whole language+IDE combination. It would replace the traditional debugger. You still have all the other concerns such as the language constructs, type system, editor, etc.

Actually, this is a design

Actually, this is a design idea that doesn't specify how the comments are specifically taken advantage of, just that all comments are executable print statements rather than strings of text that are ignored by the compiler. Also, the idea is not to document the obvious, but...as comments should...document what wouldn't be obvious to the reader of the code OR a client of the code executing it in a debug session.

The problematic part is "how is a useful comment be even more useful when its executable?" For example, some comments are attached to fields, not statements. Also, writing comments is bound to become more formal when code is involved (comments are effectively meta-code, perhaps we even have comments about comments!). Right now, "print" is the most common form of meta-code statement that we use, but maybe that is not good enough.

Now, once comments become meta-code, how do we leverage them? If they always execute, then they slow the program down, and people will see them as too expensive. Even creating a full log during debugging is probably not practical for big programs; their execution has to be more selective.

I think there is a classification work to be done on the various kinds of very different things we lump together as "comments": they're so free-form today that we sometimes don't even notice the difference. I'm in favor of destroying comments, precisely to force people to do this classification when programming, because I think specialized feature could bring a lot of value. Your suggestion above is possibly one such feature, but it's important to recognize that it addresses *some* mode of uses of comments, rather than all of them.

At a simpler, much less ambitious level, I've noticed in the past that comments suffer from not being attached to a precise point in the AST of a program. When structured comments are used for auto-generating documentation for example, various ugly heuristics need to be used to determine after the fact to which declaration a given document comment should be attached. Elisp and Python's docstrings are much superior in this regard, as they are precisely fixed to a declaration, and this enables various side-benefits.

So this is a first comment role: structured text to explain and document an *interface*, that should be attached to a precise point in an AST.

Not that not all comments can be turned into docstrings (even a generalization of docstrings with a syntax to attach them to all AST nodes, not just declarations). For example, "I will comment this line to see if the bug happens without it executing" does not fit in this category. This is a new role, commenting to temporarily disable code (that is better served by "if false ...", as you keep type-checking the disabled part, but sometimes the point is that it cannot compile in this environment (language version, etc.), in which case you need a preprocessing #if).

I don't yet understand the use-cases you have in mind for your "executable comments". You seem to be thinking of using the "execution" to display values/state, which seems is intriguing but rather obscure to me (I don't remember the last time where I thought "ah, if only this comment came with the concrete value which is used here instead of an abstract name"; I use printf debugging sometimes but they don't naturally align with comments).

The category I would naturally think of is "program reasoning" comments (logical assertions which can be checked either statically or dynamically): invariants, assertions (those aren't comment anymore), preconditions and postconditions. I would be interested in better support for those, but I'm not sure how related that is to your idea (obviously "code that must evaluate to true" is a subcase of "executable comment", but it feels different in nature). Another question is the intersection with the comment role of "explanation about the implementation for the next fellow maintainer": a part of that is about giving static assertions, a part of that is about "the code is as follows because previously we did this and we wanted to retain compatibility in such a way", which isn't purely reasoning on the current version of the code.

you could do that already? i jest, i jest.

[java pseudocode]
class InterfaceDocumentation {
public final String comment;
public InterfaceDocumentation( String s ) { this.comment = s; }
}
class SomethingThatNeedsInterfaceDocumentation {
public final InterfaceDocumentation id = new InterfaceDocumentation(
"This is a Foobar that helps us Barfizz w/out the pain of Frobbing."
);
}


Literate programming

Is it just me, or is this vaguely reminiscent of literate programming? Literate programming never sounded like The Right Thing to me, but it seemed like Javadoc was somewhere on (though near the opposite end of) that spectrum as well, with The Truth somewhere in the vast territory between them.

I thought about calling this "literate program executions." However, the point of literate programming is to "write the manual first" then extract the program from the manual, which I agree, doesn't seem to be the right thing! This is much more similar to Javadoc and other tools that extract documentation from annotated code. Except in addition to Javadoc, which extracts static documentation, you have the debugger that also extracts in situ run-time documentation about what the code is doing/has done/will do.

Flowcharts

Some years back my mother participated in a panel (I'm pretty sure it was the one on history of women in computing), and came away dissatisfied with her answer to a question about flowcharts because, she says, she left out something she really wished she'd said while she had folks' ear: that what they used flowcharts for was [in part, anyway], to tell them where to put the breakpoints for debugging.

So yeah, documentation and debugging are related to each other.

code rewriting

Here's encouraging feedback: it's a good idea to build code different ways, enabling one or another degree of log, trace, and/or debug info executed at runtime. It's common to do this in C, by hand, using preprocessor compiler switches and tedious elbow grease. The result is ugly, bloated, inflexible, and yet indispensable at times when debugging. For a function that might otherwise be only N lines long, the result is often 3*N lines long after adding debug and trace parts, making it hard to see enough code at once.

It occurred to me, when I was thinking about rewriting C source code (to target lightweight processes via coroutines), that I wanted to cause such irritating trace and debug support to be added (or removed) semi-automatically depending on declarations in whatever manifest gets used in compile and build tools. In fact, you could add language (or tool) support for declaring how you wanted things printed and traced depending on context, and depending on the flavor of trace logging indicated in code. Profiling support would get added the same way, as part of rewriting. The sort of model I have in mind resembles old-school document markup systems, like Framemaker, where you declare styles independently of text, then indicate where you want styles used. In this case, style controls "manner of logging" and anything else like it.

Instead of a lot of source lines getting consumed by noisy mess that takes so long to write by hand, you merely indicate what and how something might log, acting as hints to a compiler which removes or rewrites it for full effect. The result would be more like lightweight markup, rather than inserting a lot of finicky imperative code by hand that often has as many (or even more) bugs per line than surrounding code. Then it would stay out of the way when ignored, be more likely to be correct when generated, and be easy to re-arrange and upgrade via declaration and compiler changes.

Logging changes performance and timing behavior so much, it can cause heisenbugs to vanish (and this is actually typical). For this reason sometimes small, efficient, binary, shared memory ring buffers must be used to bleed off trace info without altering execution time so much that bugs stop reproducing. It would be nice to have this variant of trace logging also generated automatically, instead of having it as one more variant making total line size bloat up to 4*N total lines per nominal N line function.

It would be a different story if browsing was intimately part of the debugging process, when execution ran a browsable representation meant to be studied while testing live systems, when it offered directly relevant live feedback. Then I might want to see integrated docs lifted from some part of the code compilation process. But I'd be far more interested in parts deriving from live log and statistics output.

Aspect-oriented programming?

In fact, you could add language (or tool) support for declaring how you wanted things printed and traced depending on context [...] The sort of model I have in mind resembles old-school document markup systems, like Framemaker, where you declare styles independently of text, then indicate where you want styles used.

Sounds like aspect-oriented programming to me. Their obligatory example has always been how to add logging, kept separately from the code and weaved with it as part of compilation (or class-loading, for Java).

It is not aspect oriented

It is not aspect oriented programming, given that the comment code is manually woven into the program by the programmer. The code exists, it is then up to the tool only to interpret the code, or not.