67 comments

[ 3.2 ms ] story [ 136 ms ] thread
I definitely find literate programming intriguing.

I like stuff that's well documented and I find my own documentation crucial for understanding things.

I am however a bit at a loss about the practicalities.

Does writing a doc-string for each class/method/function and connecting to readthedocs count as literate programming?

Good question, and what of doc generators like Sphinx that create web pages with those comments plus the method code it came from?
"Does writing a doc-string for each class/method/function and connecting to readthedocs count as literate programming?"

I don't think so. It's like comparing the Lord of the Rings, the novel, with the index that contains descriptions of all the characters in the Middle Earth.

I mean both tell you the same story, but from a very different perspective.

I am personally not a fan of (what I understand as) literate programming, unless you are writing a piece of educational code that is supposed to be read from beginning to end (like Jones Forth, for example).

What I think should be done instead, the code should be documented on several levels, each level more detailed by a constant factor. So each couple lines should have a comment, each function should have a comment what it does with what parameters, each class (or group of functions) should have a description of their purpose, each module should have a description of its architecture and interactions, and so on until you would get to the top level architecture. So it would be more like a tree of more and more detailed documentation rather than linear novel that you read from start to end.

I would not say so. (I would also not say you should stop doing that!)

In my relatively limited experience of literate programming, it adds unique value in two ways:

1. By embedding code in a narrative which, when lucidly written, yields context and understanding of what it does (and doesn't do) and why, which is far superior to what a later developer could derive purely from analysis of the code and whatever other artifacts exist around it;

2. By using capabilities like hyperlinks, folding, and TOC/outline generation to support much more fluent navigation and autotitration of complexity than are possible without.

Learning and using tools which support these capabilities is undoubtedly more work than not doing so. Especially in a fast-paced industry environment, doing so is often both hard to justify, and just plain hard - at least in my experience, to do it well you need to be not just a good programmer but a good writer besides, and it is very hard to be that latter thing without the benefit of time to reflect and to edit. But my, again limited, experience suggests that the effort when possible can pay off very effectively.

No. Docstrings are nice, but they only (hopefully at least) explain what the current function does.

Literate Programming is more about how everything fits together, in /one/ coherent explanation.

The doc-string state pre- and post-conditions of a function and what it does in abstract terms. It should not say anything implementation specific.

For example, the doc string of "search" tells you the returned list is sorted afterwards. It should maybe tell you the complexity of its algorithm like O(n log(n)). It should NOT state which algorithm (QuickSort, TimSort, etc) it uses. Comments in the source code can explain the choice of algorithm.

Is <insert whatever type of language /> programming harmful?

What question is that?! If you write malware with it, yes it is! Though not for you.

They're just using a headline derived from Dijkstra's "Go To Statement Considered Harmful". As with Dijkstra it's a bit clickbaity i.e. they really mean use with care rather than harmful.

Making it a question just gives the extra benefit of making it likely to conform to Betteridge's law. So a meme win-win really.

It also conforms to Sturgeon's Law.
Of course, Dijkstra wasn't really clickbaiting, as clicks hadn't been invented yet.
"-3 points"? Damn! Did you lose your sense of humor ?
The trend in programming lately seems to be less to no comments and less to no documentation; and it is killing the joy I take in programming. Without comments the only way the next guy has a chance to understand the code is if you stick to lowest common denominator patterns and frameworks. You can't do anything that hasn't been done before.

I think this is related to the proliferation of frameworks. I can't come up with my own pattern that best fits the problem, but I can select from a list of pre-approved patterns in our framework and shoehorn the problem into it. We only hire people familiar with the framework, so no need to document.

It really comes down to laziness. A literate program demands you put forth the effort to understand it and read the docs and code. Whereas a with a framework program you can copy and paste code from blog posts and stack overflow without any clue what is really going on.

> I can't come up with my own pattern that best fits the problem, but I can select from a list of pre-approved patterns in our framework and shoehorn the problem into it.

Generally only true for burger flipping software. If your problem is unique, there won't be a framework for it.

Want to be creative? Try seeking out creative problems.

Everything has exceptions, but this rule of thumb seems to apply well, in my experience.

If your problem is unique and requires creative solutions, and you work in an anti-documentation team, that's when things get truly nasty.
Elsewhere known as "Academia" ;)
Which also can create documentation without code :-)
If your problem is unique, there won't be a framework for it.

That doesn't always stop the pressure to force-fit your problem into a "preferred" framework. In a large organization, both the people defining the problem and the people enforcing the constraints on the solution can be different from the people who have to implement the solution, and may not be aware of or able to understand the mis-match.

I have to disagree with this. I've not found anything of the sort you describe here.

If you have some esoteric pattern in your code, then it's never worth the tradeoff between readability unless it is better performance wise.

Calling them the "lowest" common denominator patterns instead of best practices is being dishonest.

> some esoteric pattern

I have been at many sites where "esoteric" means things like first class functions (Python) or recursion. Not some hipster monad, y-combinator or method missing bullshit.

There's a middle ground between cookie cutter and esoteric.

While the OP's tone is perhaps a bit harsh, I've been in his shoes before, where a team that doesn't understand why a problem doesn't fit their go-to solution forces you to write a convoluted mess to shoehorn the problem into the solution. performance isn't even the problem, usually. Maintainability is.

> The trend in programming lately seems to be less to no comments and less to no documentation; and it is killing the joy I take in programming. Without comments the only way the next guy has a chance to understand the code is if you stick to lowest common denominator patterns and frameworks. You can't do anything that hasn't been done before.

Enter Golang... a language smaller than even ANSI C. It's readable because if you've worked with Golang for more than a week, you basically know every language construct you'll encounter.

As an aside, I've found Clojure to be one of the most "readable" languages I've ever seen, in that I can usually just go to the source code if I'm trying to figure out how to use a library I just pulled in from clojars.

> Enter Golang... a language smaller than even ANSI C. It's readable because if you've worked with Golang for more than a week, you basically know every language construct you'll encounter.

That's not really what readability is to me. For example, I consider this idiomatic golang code (from the standard library, no less) to be entirely unreadable:

https://golang.org/src/crypto/tls/handshake_messages.go#L297

For me readability is more about suitable levels of abstraction. Languages can affect that by making abstraction longwinded, or expensive at runtime, or hard to compose.

It's ultimately subjective, but I think that code is readable: the variables are well-named and each line says exactly what it does. Abstraction makes it easier for you to think you understand what the code is doing, but if you want to really understand what's going on, you also have to understand how the abstraction is implemented. Straightforward code like this can be understood directly, without having to dig through layers of function calls.
If you think that code is readable, could you correctly write all pre/post-conditions in between statements?
I think that's a little different. Reading code almost never involves analyzing it to that degree. Usually you're just trying to figure out what part is broken or where you need to add some new functionality.
The meaning of an imperative program is given in terms of Hoare triples. If you can't produce such triples on demand, then you don't understand the program in question.
> For example, I consider this idiomatic golang code (from the standard library, no less) to be entirely unreadable

Really? It's a binary parser that's parsing data from a byte array and stuffing the results into a struct, it seems pretty straightforward.

> It's readable because if you've worked with Golang for more than a week, you basically know every language construct you'll encounter.

If you've used Brainfuck for more than 5 minutes, you already know the entire language. Is it the most readable language ever designed?

> I can usually just go to the source code if I'm trying to figure out how to use a library I just pulled in from clojars.

Will a cursory read of the source code either tell you how the library deals with corner cases, or convince you that such corner cases don't exist?

And the source code doesn't tell you why something was done. Why is this message expected to be only 42 bytes long or less? Is there a specification for it? I can see that someone grabs two adjacent bytes, pulls out 12 bits from them and puts them into a variable called "version":

  version = ((data[0] & 0x0F) << 8 | data[1]);
But why? Is this still correct with the current specification of the message format? Who knows! And this is a best case, where "version" is a meaningful variable name.
> If you've used Brainfuck for more than 5 minutes, you already know the entire language. Is it the most readable language ever designed?

Oh, come on... BF was specifically/pathologically designed to be unreadable.

> Will a cursory read of the source code either tell you how the library deals with corner cases, or convince you that such corner cases don't exist?

Um... no, not really (at least not a cursory glance), but readability certainly helps track down issues later when you do run into an unforeseen corner case. Language choice is all about trade-offs -- if things like this are what is most important to you, perhaps you would be happier with something like Haskell or Ada.

> Oh, come on... BF was specifically/pathologically designed to be unreadable.

I was just exposing a flaw in your “readability” criterion. Another example: you can learn the rules that govern a given cellular automaton in 5 minutes, yet be completely unable to predict what said automaton will do when run.

> readability certainly helps track down issues later when you do run into an unforeseen corner case.

I'd rather not have issues, and always be sure that I've handled everything. Only applied formal logic can help.

> Language choice is all about trade-offs

Normally, understandability trumps all else. The only exception is when it's too badly in conflict with performance.

> if things like this are what is most important to you, perhaps you would be happier with something like Haskell or Ada.

Nah. GHC Haskell and Ada are both ginormous languages that I don't believe anyone could fit into their heads in their entirety. In particular, with GHC Haskell, I don't feel confident saying “this code does exactly what I want and nothing else”, because someone might enable an extension I haven't accounted for, and then hilarity ensues.

(And nobody uses the language specified in the Haskell Report.)

Truly. I used Ada professionally for several years and wouldn't want to go back. Even though programs that compiled had a much higher probability of just working compared to other languages, programming with Ada was both dull (or maybe that's just an Aerospace industry thing) and frustrating (that type system, ugh).

One thing I found while messing around with Haskell is that while learning and writing vanilla Haskell is one thing, deciphering Haskell written by others is quite another, given the propensity of library authors to use language extensions and stuff like TemplateHaskell.

> I don't feel confident saying “this code does exactly what I want and nothing else”, because someone might enable an extension I haven't accounted for

If you have any examples of code that, when an extension is enabled, neither fails to compile nor fails to behave in the same way it would be really interested to see them!

If I were to try to conjure some up the first place I would start would be interactions between scoped type variables and typeclasses.

Even in the bog-standard sort of software I write, I rarely find that choosing and applying a pattern is "mechanical".

Even if you tell yourself "oh, I'll use Observer", there are still so many details that can make your solution be boring and alright, or actually clean and magical. Foresight into how your structure will evolve, making things easier to test and read, separating the _right_ concerns.

I have copied stuff from Stack Overflow but always for solutions to a couple lines of code. I don't know what Stack Overflow answer I can copy for the general structure of my code...

I guess that might be a trend in some inhouse development.

In our consulting projects there is always a minimal level of documentation specified by the customer as delivery artifacts.

They want to be sure the next set of guys are able to carry on when there is budget available again.

Completely disagree. I'm not going to summon up simple scarecrow arguments, but for software that makes money, I do not want your clever pattern with comments that saves a microsecond behind a 60 millisecond latency api. I want a simple for each loop. Where I want comments is where code gets hairy for a reason. For example, extra crafty SQL on some summary metrics page, sure, go ahead and comment that. But I don't want to see comments on more than 5% of a code base whose primary purpose is to make money.

And I'm not saying this because I've always had this view, I used to be too clever. I wrote a automatic caching and automatic windowing API client library. The thing was impossible for anyone else to alter because the mental model was too complex. Now I try to keep complexity only where it is needed and I try to keep the complexity quarantined from the interface as much as possible.

Huge projects like Rails can be clever while still making my life easier because they have an army of smart people finding the edge cases, but for most stuff that isn't the case.

> I'm not going to summon up simple scarecrow arguments, but for software that makes money, I do not want your clever pattern with comments that saves a microsecond behind a 60 millisecond latency api

Is that not a strawman argument?

You are right, that isn't worded the way I intended (not the least because I misremembered the name). I meant to say code examples. For example:

    # double the counts.
    counts.map! { |count| count *= 2 }
Is the type of strawman I intended to avoid.
I once saw

    using namespace std;  // using namespace standard
>The trend in programming lately seems to be less to no comments and less to no documentation

Hasn't this always been the case for young programmers? Seems you don't comment until you have to go back to code you wrote yourself a year later and make major changes....you then realize you should have documented it, either inline step-by-step or as a proper doxygen/javadoc style.

I can thank Dr. Leitner (CS 50/51) for enforcing comments in assignments. Took programming courses as an undergrad (I wasn't a CS major, but a Bio/Int'l studies one) in C, no documentation required...code was checked for correctness only. Leitner had his TA's go through and take off points for code that wasn't documented...which needless to say got me in the habit of documenting.

>It really comes down to laziness.

Not so sure. Laziness or lack of good habits? Perhaps a mixture of both.

Funny enough, I had a C & C++ prof deduct points for comments with the argument that they were redundant and that the code should be self documenting. Then again, this is from a guy who never handed back assignments until the end of the semester and by then couldn't remember why he graded you the way he did. Would just respond with "you didn't understand the material".
that's crap. self documenting code sometimes works...but not always. I love people that hand me HDL and say "it's self documenting." Then you open it, and wire names are "x_1245_to_y." Happened all the time when my students would turn in assignments. That stopped after they realized I graded the documentation too :).
I agree and chose to take the deductions rather than avoid writing comments. By that point I had already been in the industry and I knew better; he has never left academia.
> Without comments the only way the next guy has a chance to understand the code is if you stick to lowest common denominator patterns and frameworks.

Or

  - read the tests for that method.
  - look at the commit log and associated bugs.
  - use identifiers that are self-explanatory to some extent. auto-complete makes using long names manageable.
  - step through it with a debugger to see what it does.
  - use an editor/IDE that shows additional info about unknown methods, assuming a statically typed language
     where it has total knowledge about available types.

Comments are certainly one way to understand code, but not the only way.
Many of the techniques you listed are subcategories of reverse engineering. In an ideal world, the code and comments are clear enough that no reverse engineering is necessary.

Given a bizarre method, would you rather see a clear comment or spend half an hour stomping through a commit log?

> In an ideal world, the code and comments are clear enough that no reverse engineering is necessary.

In an ideal world, perhaps. But in the real world, the comments contradict the code, the code attempts to use a software pattern which doesn't quite fit (and thus requires a large number of hacks to make it fit), so reverse engineering is your only real choice.

Aiming for an ideal world is never a bad thing, but even if I saw well documented code, I wouldn't trust it. The only difference is that after reverse engineering it anyways, I might feel a bit of surprise when I find that the comments were, indeed, correct.

> The trend in programming lately seems to be less to no comments and less to no documentation; and it is killing the joy I take in programming. Without comments the only way the next guy has a chance to understand the code is if you stick to lowest common denominator patterns and frameworks. You can't do anything that hasn't been done before.

Not true. You just have to express it clearly. I view comments as a code smell - they generally indicate a part of the codebase that you know is poorly factored. If your language/framework doesn't allow you to express code clearly, get a better language/framework.

Laziness is one of the cardinal virtues of a programmer.

It really, really depends on what the comment is saying.

Comments that explain what the code is doing are usually a travesty. With the possible exception of hairy performance optimizations. Also implementations of algorithms that aren't well known -- oftentimes the original paper is dense, academic and behind a paywall, and the Wikipedia article are a muddled mess. You really owe it to your readers to give a sane and comprehensible explanation instead.

Comments that explain why the code is doing what it does, though, are HUGELY important. There's nothing worse than coming into a legacy codebase and seeing it littered with obvious special cases and workarounds that are all undocumented, so that you have no idea what they're for or whether they're obsolete or not. Skipping these kinds of comments leads to inexorable technical debt, because eventually you'll end up with a codebase where everyone is afraid to change the code.

I do not personally see this trend. In fact I see the proliferation of automatic-documenters and automatically checked minimum documentation requirements (which, as is often the case with blanket rules, tends to misfire producing volume but not quality; but this is another topic).

It seems literate programming assumes that the person doing development is the same as the one reading documentation. That is not always the case. And when it is not the case, the code embedded in documentation can hurt readability.

For example, I try to get all interface specs distributed, discussed and confirmed ASAP, usually WAY before the code is mature enough to be shown outside. Also, getting others to read, think about and comment on even short ICDs is difficult as is. Getting them to read early ICDs interspersed with early code? Not going to fly. My 2c.

I haven't figured out literate programming with a large group yet, but I do use it (with org mode) for my own projects (solo or small group). A nice thing, at least with org-mode, is that I can choose whether or not to export a particular bit of content.

  * This headline won't export      :noexport:
    These are some comments for the user and maintainers
    of this document. Describing our formatting and
    practices.
  * This headline will export
    And here's the text describing some message format.
  
    And here's a table showing the layout:
    |  Word  |  12-15  |  8-11  |  4-7  |  0-3  |
    |   0    | WordCnt | TimeTag| ...   |  ...  |
  
    And here's the source for this as a C struct, but
    it's a work in progress. So we won't export it yet.
    #+NAME: MSGXXX_struct
    #+BEGIN_SRC c :exports none
      typedef struct {
        ...
      } MSGXXX_t
    #+END_SRC
No one has to see that stuff until I'm done with it. Or, other tags could be used so I can explicitly select what gets exported depending on the audience. The customer doesn't need the code (at this point). But fellow devs (local, or at partner organizations) may.
That is reasonable for a solo project, which naturally has a common style readable to the author.

In my (maybe skewed) experience though once 2 or more people start adding comments on the same place of code things can get very confusing quickly -- with mishmash of different styles, commented out comments, multiple copy-paste, etc., so the only way to understand what is going on is to delete the whole Kunstkamera and look only at the code.

NOTE: The plain text mode here doesn't do the org-file content I've included justice. Copy it into emacs and switch to org-mode to see how this'd actually look and work. And I agree, right now I've only figured out how to do this with myself and one or two other people. I use this for both work and side projects. I also tend to work on smaller code teams (max 3 people) so that helps if I want to use this at work on a specific project. You also have to get buy in on the tool (emacs and org-mode, in this case, which is a hard sell these days). I imagine there are ways to make this scale, but I've not figured it out yet. It'll require more tooling and finer-grained access for editing. Merging with this in git could be a pain in the ass with more than a couple people.

You can get code only views, no deletion necessary. Run tangle, which will spit out all the source files (which is part of the compilation/execution step anyways), or use (org-mode specific): C-c C-v C-v (org-babel-expand-src-block). The latter gives you the expanded (this assumes you're using something like noweb[0]) view of the code in a read-only buffer.

Org uses optional noweb notation to include blocks of code in other blocks:

  #+NAME: print_hello_world
  #+BEGIN_SRC c
    printf("Hello, World!\n");
  #+END_SRC

  #+BEGIN_SRC c :noweb yes :tangle hello.c
    #include<stdio.h>
    int main(int argc, char** argv) {
      <<print_hello_world>>
      return 0;
    }
  #+END_SRC
<<name>> will be substituted with the contents of the other block. The expand-src-block will give you a view with all substitutions made. C-c C-v C-t (org-babel-tangle) will generate hello.c (could be prefixed with a directory, my preferred way, so it'd be "src/hello.c").

A more useful case than the above, I have a switch/case statement. Each case gets broken into its own source block with documentation around it like:

  * Handling different message types
  #+BEGIN_SRC c :noweb yes
    switch(msg.type) {
      <<msg_101>>
      <<msg_102>>
      <<default>>
     }
  #+END_SRC
  ** Message 101
  Documentation
  #+NAME: msg_101
  #+BEGIN_SRC c
    case 101:
      // handle 101
      break;
  #+END_SRC
  ** Message 102
  Documentation
  #+NAME: msg_102
  #+BEGIN_SRC c
    case 102:
      // handle 102
      break;
  #+END_SRC
  ** Default case
  Documentation
  #+NAME: default
  #+BEGIN_SRC c
    default:
      // handle default
  #+END_SRC
I use literate programming for "research" projects - I use emacs org mode with org babel and ob-ipython. It is useful for that purpose, as I am usually chasing a few options in parallel and results later will need to be presented to the team.

When project wasn't that researchy in the first place (does not require significant experimentation to come up with the solution), or exits the research phase I move to standard programming tools.

The value of literate programming is proportional to the novelty of the material it documents.
My new checklist for making programs easy to understand and work with in the future.

- Make the program in complete slices of a functionality and put those into folders and files that are descriptive of what the code does: "web/login/facebook/always_ask_for_email.lang"

- Do repeat yourself in different functionality areas.

- Name your variables and functions as if they should never conflict/as specifically as you dare.

- Use a functional and immutable programming language or write pure functions as much as possible.

- Stop thinking that patterns are necessary to solve the problem of turning data from one form into another.

I might be wrong.

I feel bad to say this, but the practical world has burned me so many times that I simply can't trust anything said after a comment delimiter. If it's not in the code, it doesn't have any impact on what the code is actually doing, making it redundant at best, and misleading at worst.

For that reason, I find myself constantly reverse engineering code. If I am tracking down a bug and run across a bit of obfuscated code which has the comment "optimized hotspot sorting", I'm going to assume that it's wrong. I'm going to take the time to reverse engineer that obfuscated code, to make sure that the code is actually sorting, that it it an actual hotspot, and that it's not the cause of the bug I'm chasing down.

I guess I use it as a negative signal: "the comment says the code does X, I need to verify that it actually does X, correctly". "Optimized" code blocks and comments taken as the truth have pointed me at more bad code than good code.

I recently ran across this piece, "Literate programming: Knuth is doing it wrong", whose author contends that

> the ends are insufficiently ambitious by focusing on a passive representation [0]

After practicing literate programming seriously for over a year on a large project, I believe that our notion of documents themselves is insufficiently ambitious, for the same reason.

Computer-based media should not be limited to the static forms imposed by paper. It's unfair to critique literate programming per se until we have a truly dynamic mode of representation. (edit, yes, technically we have that now, but it's an order of magnitude more work to create. I'm saying that live, dynamic representations should be our tool for thinking, building, and writing about things in the first place, not after the fact.)

[0] http://akkartik.name/post/literate-programming

What tools did you use for this?
How long do you have?

This was for a project called "willshake." The system provides a rough overview of itself [0].

I was using Org for documentation, and I decided to try migrating to a literate codebase.

It was clear immediately that I needed a "real" build tool. After some research, I decided that was Tup [1]. Tup is a strict, "functional reactive" build system with extremely fast incremental builds.

So I run the Tup monitor, and everything gets tangled as I write. Great.

But tangling through Emacs/Org was way too slow. So I wrote my own gawk-based tangle [2] that supports the subset of Org Babel's features that I needed, but would tangle any file in 20-50ms.

But usually I want to do something with the tangled code (you know, compile it, run it). So I also define build rules inside of the documents (for example, [3]). I just extract the rules from the documents and feed them to Tup.

Now I can just write whatever I want in a document, and stuff just happens as I write. This includes the removal of obsoleted build targets, which Tup handles. So I can "literally" remove a document file from the project and the feature is removed; return it, and the feature is restored.

But as the system grew, the build started to push 2 seconds. This is because Tup was reviewing thousands of rules whenever any file changed. So I decided (following Christopher Alexander), to treat directories of documents as isolated subsystems.[4]

So I can create features and subsystems with continuous builds ~250ms, using only Org files and directories.

It still sucks. I'm taking a detour to explore dynamic documents, which I think precedes considerations about "programming."

[0] https://willshake.net/about#more-about

[1] http://gittup.org/tup/

[2] https://bitbucket.org/gavinpc/willshake/src/default/bootstra...

[3] https://willshake.net/about/the_stylesheets#sec-4

[4] https://bitbucket.org/gavinpc/willshake/src/default/bootstra...

Thanks for the response. What you've written here seems like the direction I'm trying to explore. However, the corporate proxy server seems to be having trouble with your links (they don't like bitbucket, don't know why), so I'll have to review those after work.

EDIT: Thanks for the tip about tup. That looks like something I could get into. Been reading about it some this afternoon, I'll check it out more tonight or this weekend.

Finally had a chance to review your various links. That's a very impressive project, I like it quite a bit and have now shared your product (willshake.net) with several friends.

Thanks for the links and the resources. My target with literate programming is a bit different than yours, but you've given me some things to think about. I appreciate that.

Thanks, it makes my day to hear that. I think literate programming was ahead of its time in some ways, including ways that maybe Knuth didn't anticipate. Good luck.