Hacker News new | past | comments | ask | show | jobs | submit login
The Most Important Code Isn't Code (zachholman.com)
95 points by holman on June 7, 2011 | hide | past | favorite | 79 comments



Ok, I'll be the curmudgeon here.

In recent software development efforts I have run, I have put for the rule that "All comments are bugs". Comments get separated from the code, make statements about obsolete activities, and often mislead the reader, and even sometimes the author.

In place of comments, write code that is as self-explanatory as possible.

I refer to Martin Fowler's "Refactoring" as a way of trying to increase my authority in the matter.

However, I do back off from this extreme position and put comments on individual methods. Sometimes. What helps is using longer method/function/attribute/variable names than pg or tptacek would.

I think the TomDoc example is particularly poorly chosen for the author's thesis, as, more often than not, one is likely to just do "text * num_duplications" inline, no? So why is this even a separate method? Certainly not to save lines of code.

As noted in other comments to this submission, the real value of comments is the why of doing something unusual, not what.

And who was it that said that the Ruby community should spend more time on documentation rather than tools for documentation? A case in point is the doc for EventMachine. This is an insanely useful tool, yet every time I look for the answer to a question about how it operates, the auto-generated documentation leaves me disappointed.

If you are going to spend time on documentation, there is the place to spend it. Make your code readable in its own right.


I would change "All comments are bugs" to "All comments document bugs we have to work around." Otherwise, some maintenance programmer is going to wonder why, only for Solaris, I use poll() on a single file descriptor when I immediately call recvfrom(). With a comment, I can inform said maintenance programmer that under Solaris, the observed behavior is that recvfrom() is NOT a pthread cancellation point, but poll() is, whereas under Linux, revcfrom() IS a pthread cancellation point.

In reading over the comments I've written, a majority of them document the various methods of working around bugs on third party components we can't fix. Ah, the joys of working with proprietary, non-source libraries.


And this sounds more like the why kind of comment rather than the what, which approach I agree with. That is, you are commenting the pieces of code that a reader would say "er, the heck?" with explanation.


I find your comment valuable and largely agree, but it spurred me to write out an objection I have to a common view (not necessarily yours).

What helps is using longer method/function/attribute/variable names

Sounds great, so why don't all good programmers do that? This is a deeper question than it seems. Over time, I've come to mostly prefer short names. The reason is that longer names add lexical noise to the code; they distort its structure and thus drown out other important information. The lexical is only one of several semantic channels and there are tradeoffs between them: you can't optimize clarity via verbosity. After a certain point (rather quickly, in fact) inflating the code detracts from clarity overall. It's easy to miss this because one often is making X more readable by giving it longer names or spelling its logic out in detail. The trouble is that other things than X have now become more obscure. The question is, what maximizes the clarity of the program as a whole?

I'm pretty sure my code would evoke howls from the "all code must be immediately readable [to me]" brigade. I used to feel the same way, but now I don't. It leads to sacrificing deeper comprehensibility (of the entire program) for superficial readability (line-by-line). Maximizing the overall clarity of a system is closely tied to distilling its parts, and the connections between them, to their minima. Code inflation inhibits this.

The demand for immediate readability comes from a belief that all code should reveal its secrets immediately. That would be great, except it's impossible. There's a fundamental complexity tradeoff at work. If you opt for verbose readability, you end up with code that is line-clear (I know this function is saving a record to a database or whatever) but system-incoherent (why the hell is it going to the database here?)

Talented programmers who care about readability but have a superficial view of what that is end up producing systems with far too much code. They accrete code, which may be impeccably pseudo-readable, when what they ought to be doing is distilling it. Such code is like the old joke about the person lost in a hot air balloon who calls down and says "where am I?", but the guy they're talking to is a technical person who answers, "you're in a balloon twenty feet above the ground".

Programs that are built for global intelligibility usually have much less code, but not necessarily the sort you can scroll to and immediately grok. You have to work to begin to understand the program, but once you absorb its conventions understanding proceeds much more rapidly. Latency is worse this way, but bandwidth is orders of magnitude better. The reader I feel responsible to is the one who is willing to put in this work. After all, they're going to have to do it anyway to get anywhere nontrivial.


What do you think of short methods?


A friend of mine who was a smalltalk programmer for 17 years told me that the median length of his methods over his career was four lines. I think that this is admirable and wish my code were more like that.


It seems that having many small methods trades one complexity for another. For methods that are only used once and sequentially, it seems to me that it's easier to follow if they are all inlined (by the programmer). If the methods can be reused elsewhere, it's a different matter.

On the other hand, methods are modules, giving syntactic and compiler-supported semantic separation - and you can do things like return early.

I can't tell which is better.

Some people claim that short is better, but it always comes across as a bit rabidly dogmatic, because... well... it's without evidence. There is so much of that in comp sci: design patterns, functional programming, editor and language choice. Most people giving opinions don't even mention what type of task that advice is relevant for, nor give their experience that supports their choice. (It's easy to argue for a choice - smart people unfortunately can argue convincingly for anything.)

I tend to use separate methods only if they are reusable (otherwise it's a waste of time making them reusable). I often think of better ways of doing things, so I don't like to invest too much in what I have now. I'm mostly writing prototype code for new kinds of products, not "production code", not for clients, and no one sees it but me. Much code I've seen that it made of many methods and classes looks horribly over-engineered to me, especially when the problem itself is actually very simple if you approach it in the right way.

I'd love to hear gruseom's opinion tho.


I wrote my thoughts up above, but will respond to this here. I agree, both about the short methods school and about programming dogmas in general.

I went through a couple years of working in the OO short method style. Recently an old client called me back to help modify some code I'd done for them in 2004, so I went down for an afternoon to help them out. I was really embarrassed. It was obvious to me that I had strung things together in endless chains of delegation (tiny classes and short methods) not because that was simple but because it, at the time, was my belief about programming. I got that belief from other programmers I admired.

The truth is that this is how most of us work all the time. I don't mean short methods; I mean picking a style based on our beliefs -- mostly for emotional reasons -- and then seeing the entire programming world through that filter. To be able to just see the problem is difficult when you're operating from one of these positions. Much (most?) of what we do in software development is add extrinsic complexity, which is bad when the problems themselves are hard to begin with.

My experience is that your moment of "the problem itself is actually very simple if you approach it in the right way" does eventually come, if you make getting there a high priority. But it's challenging. Most of the time we don't even know what our assumptions and beliefs are, let alone have the flexibility to adapt them to the problem. It's usually the other way around: we adapt the problem to our beliefs because they determine how we even see the problem in the first place.


I think one key thing to keep in mind is that it is better to chose a style that is more likely to obviously have no errors, rather than one that has no obvious errors. (Wirth?)

With longer methods, it becomes more strenuous to say that it obviously has no errors.

And in It seems that having many small methods trades one complexity for another. is not a fair representation, as the implied relationship is not linear.


With longer methods, it becomes more strenuous to say that it obviously has no errors.

I disagree. The proper comparison is not between one long function and one short one (that's a no-brainer), it's between a long function and a corresponding set of short functions plus all their interactions. Posing the comparison correctly makes the complexity tradeoff look very different. I'm not saying it's obvious, but the prima facie bias goes the other way.

There's a shortcut for answering this kind of question that may not be infallible but is very useful: program length. Things that make a program longer tend to increase its complexity. One should hesitate to argue that something which inflates code size is making a program simpler. But that is what the short-methods-OO school does routinely.

I don't see why one can't take overall program size as the basic measure of total complexity.

Edit: from another comment in this thread I gather that you tend to see interactions between functions as less complicated than code inside functions. Boy, do we look at this differently! If a function can do a single meaningful thing in isolation, of course I'd factor it out (that's almost another no-brainer). Those are what PG calls "utilities" in his Lisp books. They're meant for random access. But when functions start to interact with too many other functions in ways that affect application logic, my complexity Geiger counter goes crazy. I'd much rather have those interactions isolated in one place, where nobody else can get random access to them and introduce even more dependencies. As befits a truly different world-view, I'm puzzled as to how you can even hold yours.* It seems like a simple matter of combinatorics.

* Doesn't stop the discussion from being delightful though. Just to be clear.


I like how you qualify program length as a fallible measure (they're more guidelines). Similar to your story of overdoing tiny classes and short methods, due to a belief from programmers you admired, I once dogmatically followed this as a rule, always extracting code than was used more than once, and only doing so then. I think that's a standard learning stage, where you learn something, over-apply it, then learn distinctions for when it is appropriate and when not.

Optimizing for length is only one criteria - optimizing for clarity is more important (strange observation: in writing, redundancy enhances communication); optimizing for flexibility/change is another. I like the idea of just expressing your current understanding, very simply - not weighed with suspect prophecies. Change it as your understanding improves; as you reuse it. Brooks observed that having a spec and different implementations leads to a more robust spec; and there's an idea of not generalizing code until after you're implemented it three times, for different purposes. This is the opposite of architecture astronautics - being grounded in actual instances of concrete experience.

So, I give up a simple, single theory of how to code, and I'm lost - which is perhaps an accurate appraisal of our current understanding of programming. Only the actual details of a problem guide you.

From what you said elsewhere, I think the simple key is to keep focusing on the problem, not the program. My old supervisor said I was over-concerned with theory. "Look at the data!" he admonished me.


C.A.R. Hoare (according to http://www.gdargaud.net/Humor/QuotesProgramming.html anyway), but I prefer Douglas Adams' formulation, though less clever: their fundamental design flaws are completely hidden by their superficial design flaws

Yes, it's true that the compiled-supported semantic modularity of methods helps here: e.g. it can't access other methods' local variables, you can see what goes in and what goes out. But, in a long method, you can manually enforce the same modularity on sequential parts (you can even use local variables scoped by {} to borrow some compiler support). But, yes, point taken.

Can you elaborate on the relationship not being linear? I think you mean that the many parts of a long method can interact (if the coder doesn't enforce this manually).


Elaborating: Less complexity in each method leads to less overall complexity. And yes, the worst of long methods is that the internal interaction usually leads to more complexity.


I was asking about the "non-linear" part.


Perhaps poor choice of phrase. It is my feeling that moving complexity out of methods and possibly having it in the interrelationships between methods results in a net gain in understandability. Perhaps asymmetric would have been a better word?


It codifies the relationships between the parts - but also adds ceremony (the call, the method definition, the arguments). Whether this is a net gain depends. Probably the key is whether it feels like a conceptually distinct component - a theory or abstraction that you can build on. This is probably true even if it isn't reused (but typically coincides with it).

There was some "non-linearity": the interaction between parts increases (roughly) with the square of the number of parts: 4 parts have 16 directed interactions. Combinatorial explosion is a more accurate measure. If you can separate the parts into modules, that interact with only the caller, then the complexity increases linearly (but if many methods need to interact, then it's as if you wrapped all the cables up in one tie, and forced them all to go through the "main" method - not actually an improvement. However, it's rare for everything to interact with everything, and even then, it may be clearer to codify it somehow).

I also wanted to say that while proving correctness is important, the organization that is best for proving is not always the same organization that is best for human clarity (sometimes they are). It depends on what your goal is. Proving absolute correctness is not important for most programs (all software has bugs; bugfixes are common) - to sacrifice human clarity for correctness is usually not the optimal trade-off.


You're talking about avoiding temporary variables (see the refactoring book), which is an independent feature. Just talking about method length will only illuminate so much.


Hell, this reminds me of another thing I want to say. The word that leaps out here is "smalltalk". The short-method school of OO came from the Smalltalk world. It has since been extrapolated to other languages. I am skeptical of this extrapolation. It's fashionable to say language choice doesn't matter that much, but I think language choice has a powerful effect in conditioning how one thinks about one's program. Different languages give rise to different ideas, and ultimately very different programs. It wouldn't surprise me if the short-method style makes good sense in Smalltalk environments for reasons that don't naively extrapolate to other languages. But that's just a guess.

By the way, the reason the above language effect doesn't get discussed is that when we compare languages we're almost always talking about already-existing code (look at how you can write X in Haskell or whatever). This leaves out the most important factor, which is how the language affects the creation of X in the first place.


With respect to specificity of languages, Uncle Bob in "Clean Code" also advocates for extremely short methods in C#, on the same order as what my friend uses. Different languages do give rise to different ideas, but I think that what matters more is how that language thinks about Objects and Classes, as one example. For example, in Smalltalk, methods are messages. In C++ and other contemporaneous langages, nobody talks about messages. They are still procedural chunks, more or less. This fact has a very big influence on how programs are written.

Then in CLOS, you have generic methods that don't belong to a class, which is really a third view quite independent of the other two ways of looking at OO. And you get multiple inheritance without any confusion. And taking a look at those suckers, they are generally not very long at all. I have a bunch that are zero lines outside of the defgeneric part.

So short methods are also very evident in PGs code. While slightly longer, they are the equivalent of the four-line smalltalk method, and brutally simple. Arc takes this to the next level. Kind of like writing Lisp without vowels.


I like your phrase "how that language thinks". C# doesn't think like Smalltalk, so it's a mistake to write C# code as if it does. "Uncle" Bob is not a reliable source here.

It's been a while since I've read any of PG's code, so the following may be off-base, but I don't think he has any heritage in the OO-short-method school. In his books, at least, he's usually looking for functions that work like language constructs. The hallmark of such constructs is that they can be composed orthogonally. This is not at all true of the long delegation chains favored by classic OO.

As for brutal simplicity - a thousand times yes! But what we're discussing is how do you get it. More precisely, how do you get it at the only level that counts: the whole-program level. Otherwise you're just shifting complexity around, and probably thereby increasing it.

Edit: Two addenda. (1) I'm surprised to see PG's code come up in the discussion the way it did, because his writings were probably the thing that cured me of OO, or at least convinced me to check myself into detox. (2) I just had the scary thought that someone might read this discussion and think that I'm arguing against short functions in favor of long ones. God no. I'm arguing in favor of short programs against longer programs that have many more function definitions and interactions. Somebody assure me that was clear.


Well, reading between the lines, PG thinks very little of OO.

Uncle Bob's book Clean Code demonstrates very small methods in C#, and it does go in the direction of looking for language constructs. It is just harder to get there in a non-lisp language.

Regarding OO in general, I am a former proponent of OO--in fact taught (delivered might be a more accurate description) the Rational Unified approach several times, brought OO thinking to several business units. Reading PG and getting heavily into Lisp took me away from that.

I think we disagree slightly on "whole program complexity" and the increasing complexity of shifting it around.


You may be interested in my comment above about my experience: http://news.ycombinator.com/item?id=2648279


For fun, I'm going to write my thoughts before reading what you said about it elsewhere in the thread.

What do you think of short methods?

I'm skeptical of them. I think it's a mistake to try to make functions short for the sake of making them short. It's a mistake because adding a new function also adds complexity (i.e. more code, plus opacity between the calling and called) - not a lot, but greater than zero - so introducing a function is not cost-free and its benefit needs to be greater than its cost. I found that once I started asking functions to justify themselves this way, I began creating fewer functions and the overall complexity of my code went down.

Factoring code into functions is one of the best tools we have, of course, but people commonly make the mistake of applying it mechanically. A function should exist when the program itself wants that concept, not because you had a block of code that was too big or some duplication and you wanted to rearrange the pieces. The way to address those symptoms is not by adding more code but by thinking until you see how you were looking at the problem wrongly. Then the new concepts, and finally the new functions, appear by themselves.

You only have so many conceptual cards to play and must play them sparingly if you don't want your program to succumb to runaway complexity. A good function is a logical construct that makes sense in the meaning of the program the way a good word makes sense and adds a unique meaning to a language, something you can't quite say as well any other way.

When all you're doing is shifting pieces around, you're missing the most important thing about functions, which is this conceptual payload. After you do that for a while, your program stops evolving as an expression of the problem being solved, because you've built it out of primitives that refer only to the internals of the program rather than to concepts drawn from the problem space.

Side note. I'm writing at such length here and in the GP because these questions are on my mind all the time. I've been working on a hard problem for over two years now in an utterly immersed way, the kind where you dream about it every night, where time itself begins to blur. Our approach has been to evolve the program many times until it converges on a solution. The only way to do this is if the program doesn't grow as you evolve it. How do you build a system such that you're constantly adding new information and behavior to it, and yet the overall code doesn't grow? We've had to figure this out just to stay alive.

One more thing about function length - Steve McConnell cites studies that suggest that short functions aren't easier to understand. IIRC the sweet spot was between 50 and 100 lines, depending of course on the language. I've posted references to this on HN before. One should be careful about believing these studies because the empirical literature on software development is so poor. But it's at least interesting that such experimental evidence as exists runs counter to the "OO short methods" school.


> After you do that for a while, your program stops evolving as an expression of the problem being solved, because you've built it out of primitives that refer only to the internals of the program rather than to concepts drawn from the problem space.

Wow, nicely said.

You also remind me of the problem of removing "accidental duplication", or overfitting: this is when you factor out common code, but it turns out later that it's not really common - lots of minor and sometimes major distinctions occur as you implement more of the problem. It was only by accident that the code happened to be the identical at that stage of development. The theory constructed (the factoring out) gave too much weight to limited information (an early stage of the program), overfitting to that specific information. Generalizing from two instances is almost as bad generalizing from one. In your terms, it models the program not the problem.

It's so refreshing to hear similar thoughts to mine. :)


http://lists.canonical.org/pipermail/kragen-hacks/2011-June/... has an example of this "eliminating accidental duplication", I think. The first version of the program treats "note handlers", the end-users of the "notes" it distributes, as simply one kind of peer. But the later versions have a more complicated protocol between peers, and so they reify "note handlers" as a separate kind of thing. Similarly, the first two versions invoke the user-visible .publish() method when they receive a note from a peer, but the third version has factored out a new .got_note() method to factor out the duplication between the two code paths.

As I was writing this code, I was thinking about Uncle Bob's extremist viewpoint on short methods in Clean Code, and I tried it out. In the end, I inlined all the methods that had only a single caller, except for the handle_message family. I think the code came out exceptionally clear and simple, but Uncle Bob would not be happy with it.


The empirical studies you're mentioning were of FORTRAN functions, I think from a linear algebra library. They may not generalize.

My thought is that short methods make your code more flexible — that is, you can compose the pieces of it in more ways, so the next bit of code you write without modifying the existing code can be shorter — at the cost of comprehensibility and verifiability. It's no surprise that this value came out of the Smalltalk camp, because Smalltalk (and OO in general, but especially Smalltalk) is optimized for flexibility at the expense of verifiability.

When you factor out a method, you're making the code you pulled it out of easier to read — except when the reader needed to know the details of what you pulled out. But you're making the code you pulled out harder to read, because the reader no longer knows that it's called in only one place, what the state of the system is when it's called, what the values of its arguments are, and what its results are used for.

There was once a school of thought that it's easier to read a piece of code if it's laid out to visually show the tree structure of its loops and conditionals, and if it uses loops and conditionals instead of gotos. I think this is not the only virtue that code can possess that helps its readability, but it is a real virtue. Factoring out more methods reduces this virtue, so it needs to be repaid by some other virtue, which I think is what you're saying.

I have several different heuristics for when it's good to factor out methods or functions, but I think they aren't good enough, because I always end up with some functions that are kind of a mess.


The documentation example for the multiplex() function seems like massive overkill to me. The most informative part of the documentation comment is the line that starts with "Duplicate some text..." So why not just name the function duplicate_text() and be done with it? The arguments could be documented similarly, by naming them "text" and "num_duplications".

I don't think I'd need an 11 line comment to tell me what this definition did:

  def duplicate_text( text, num_duplications )
    text * num_duplications
  end
In fact, I might prefer NO comment, because comments can become out of date and misleading, whereas the code always tells the truth.


Not to mention the fact that the word "multiplex" already has a meaning, and it has nothing to do with concatenating duplicates of a string. Unless the function name is intended to be parsed as "multiple 'X'".


Rather ironic that the function "multiplex" as "multiple x" is included in an article that says:

"If you don’t have an absolute clarity in the code you’re pushing, it rears its head by way of bugs, confused coworkers, and slow code."

That's the single most confusing function name I've seen in a while.


Well, comments serve a purpose. For example, if you checked that function into my codebase, I might comment thusly:

    def duplicate_text(text, count):
        """humbledrone, you effing moron, just inline
           the effing astericks"""
        assert False


That was pulled from the TomDoc spec- it was designed to demonstrate TomDoc, not the code. Having an overly complicated implementation makes explaining the documentation side of things a bit more difficult. :)


Ah, I see, it's an example of how to, not when to. :)


But "TomDoc is a mentality". What the heck is "TomDoc" anyway? The author says it's a mentality, but since when do mentalities come with specs? As far as I can tell, it's an example of a function spec that's so badly written that it needs a huge comment to explain it. Lousy name, lousy parameter names, no statement of what it returns, no actual return, etc. Maybe the author just needs a language that declares returns and some practice at naming variables/functions?


But that is a good point you make :) Is it maybe not the documentation that matters so much but the semantics communicated through the code itself?

Documentation is important, its just how its done today that bothers me. I have been reading the book "Computational Semantics with Functional Programming" and it has been quite intriguing. I would highly recommend it.


Even in such a small function comments may serve a useful purpose. For example, they could describe edge cases, when applicable ("a null text is considered an empty string" versus "a null text is an error", or "a zero number of duplications returns an empty string" versus "...returns a single text" and so on).


For a senior coder, comments may feel redundant but for the junior coder, it helps. If they can't comment the program from beginning to end, they haven't thought the logic through enough to write clean code. And what I like about well written comments, it's easier and faster to either modify or have someone else "step in" with the coding project if necessary, and use to explain to a non-technical manager where you are and what you are doing.


While I mostly agree, breaking out a comment for every parameter tends to wordiness and to encourage you not to see and explain your purpose synoptically. Like, this:

> Perform an n-fold frobulation. > @param n the number of times to frobulate > @param x the x-coordinate of the center of frobulation > @param y the y-coordinate of the center of frobulation > @param z the z-coordinate of the center of frobulation

could be "Frobulate n times around the center (x,y,z)." (From http://stackoverflow.com/questions/499890/what-is-your-perso... )

So the example would go "Return text concatenated count times." I'd try rewriting the longer example from a diff message in the OP, except it didn't fit in its own snippet!

A wordy style makes writing and maintaining comments feel like a chore; feeling like a chore, it gets done less. People start finding reasons comments are bad, and taking them for the whole story.


I can't express how many times I've seen code with comment block boilerplate at the start of every method. And nothing filled in. Its really more common that the other kind.

So sure, we all know its great to document. The challenge is figuring out how to get it done. Given that we're all in a hurry, mean well but think we'll get back to it.

It feels like you're doing something when you paste in all those blank comment blocks. But soon you don't even see them, since first off they're usually blank, and second when debugging you're looking for the code not the comments, since if it all worked like its commented then there'd be no bugs.


>since if it all worked like its commented then there'd be no bugs.

Unless it is no longer supposed to work how it was originally written and commented. Software lives and evolves over time. All too often comments go stale. When the Big Bug Day comes, many hours will be wasted on such legacy comments.


At some of the more orderly places I've worked, we put dates on comments.

Also, whenever we made a change, we put a comment (at the top of the class, function, method or whatever is changed), also timestamped, with a reference to the change request ticket (or equivalent), and a summary of the change.

Old comments were deleted if necessary (you could always look them up with a diff on the source control)

Said ticket also had a reference to the code file :) so you had 100% traceability.

The downside being of course it was all very bureaucratic, but there were legal requirements to fill and it was a sensitive, finance sector company. That setup probably wouldn't work for a fast-paced startup :)


As I understand it, the difference here is that the documentation is actually tested automatically. I believe Python has a tool like this too.

Edit: or maybe not, I can't see mention of it on tomdoc.org. However the format lends itself to that.

For contrast, the D language allows inline tests.


Even better than documented code is code that's so clear it doesn't need explanation, with occasional comments explaining the complicated bits.

The other case is API docs for libraries and frameworks meant for external consumption.


I absolutely agree. I don't comment all that much, but comment where it's impossible to get clarity -- or comment at a higher level to explain the rationale for the code.

Commenting what code does -- is either 1) pointless or 2) indicative you should rewrite your explanation-requiring code in a more straightforward fashion.


I comment for one person and one person only: myself in 6 months or 2 years when I've forgotten everything about this code! Strangely enough, that kind of attitude results in comments that everyone finds useful.


I actually comment even simple code because comments show up a different color in my editors. Comments for me are often an additional mnemonic trigger rather than necessarily a store of detailed information. It's like indenting or bolding text. It helps with "chunking" while skimming code.


The trouble is that the very same visual cue makes it easy to filter out comments as background noise, so people fail to read them (or even see them) when updating the associated code. As a result, comments don't get maintained and become inaccurate over time. It takes only a small amount of such semantic decay before the comments are a net negative.

Programmers take code seriously. Most don't take comments seriously, even when they believe they ought to. You might say they're bad programmers, but I don't think it's that simple. I think the attention goes straight to code for a reason.


For this purpose, whitespace is a much better tool than blocks of lorem ipsum.

Braces (in relevant langauges, like C and Java) are even better, because they tighten scoping and avoid unintentional variable re-use.


An experienced engineer told me the purpose of documenting is not to tell what the new few lines do, it is to explain something that does not look right, or something to be careful with.


Other people have already said most of what I wanted to say – that self-explanatory code, and sometimes tests, is almost always better than comments. I just want to point out a good reference on this topic: the book Clean Code: A Handbook of Agile Software Craftsmanship [1], specifically Chapter 4, “Comments”. That chapter gives examples showing which comments should be turned into code and which are acceptable to leave as comments, and it explains the reasoning behind the choices. (And the rest of the book teaches you how to make your code self-explanatory and easy-to-read.)

[1] http://www.amazon.com/Clean-Code-Handbook-Software-Craftsman...


The most recent breakthrough in my coding style has been naming methods and variables so that documentation of anything other than input expectations is largely unnecessary.

In your example something like textByRepeatingText(text, times)


Sorry but I had to think really hard to figure how the name textByRepeatingText could have been thought up.

It might fits your thinking, but it certainly doesn't mine. Documentation is the common language (hopefully).


Most discussions about commenting necessarily revolve around how to comment functions/methods/classes, when to comment, how to write code that is so intuitive that it doesn't necessarily require commenting, etc. In my opinion, these discussions miss out a key documentation requirement: describing how everything fits together.

Let's assume the supposed best-case: every single method and class is commented using a language-specific documentation system (I'm thinking of EDoc for Erlang, Javadoc for Java, etc). Who cares? Congratulations: I now know how all your functions work. But what is the system architecture? How do object instances of the different classes hang together to support the functional specifications? What is the sequence of events for various successful and failed transactions?

I suppose what I'm complaining about is the rationale that code commenting can necessarily substitute a solid set of functional specification and design documents. Then again, noone here made this suggestion, so maybe I'm just whining into a black hole. The only open-source project that comes to mind when I think of an astoundingly high quality of documentation is SQLite in the form of "Inside SQLite (2007)" (O'Reilly); unfortunately it's not free.


Good documentation is great, but its importance grows with the size of the project. It isn't as important in a Rails app where only a few people touch the code and the concepts already well known about the framework.

The project can grow both in terms of code size and the number of people engaged. Each drives up the importance of documentation.


I think thar the most valuable documentation are the commit comments for the version control system you use.

Especially if one uses the incremental iteration paradigm, the commit history together with the code diff IS the documentation of one's code in it's most fundamental way, that is when I changed something, why, what I was ( thinking that I was ) doing etc.

In case that one has to maintain existing code, of course, the whole history is not available but still, refactoring committed similarly documents the evolution of the code in a similar way.


This is also known as "literate testing", as in Python's doctest. I have collected some information on it here: http://arrenbrecht.ch/testing/.


Great article. However, you should be using YARD. Along with: https://github.com/lsegal/yard-spec-plugin


I couldn't agree more. Writing documentation tells me more about my own code than any level of testing ever has.

Both are important of course. ;)


You mean to say that a test like the following wouldn't be better than the comment for multiplex()?

  unless multiplex('Tom', 4) == 'TomTomTomTom'
    raise TestError( 'multiplex() failed' )
  end
Not only does that test communicate the exact same thing as the documentation comment, it is guaranteed to be correct and not out-of-date (assuming it's run as part of a test suite), whereas the comment can easily be wrong.


It doesn't communicate the same thing. It communicates one example, leaving the reader to guess how to generalize it. Examples are good, testing is good; and specs are good too. It's currently popular to valorize the first two at the expense of the third, but I think this was an overreaction to the older dogma.


If the general behavior of multiplex isn't clear to the api-user from the example:

    multiplex('Tom', 4) == 'TomTomTomTom'
I'd argue that's a failure of the api designer that no amount of documentation is going to make up for.

Examples are good, testing is good, executable, testable documentation is doubly good, and predictable, intuitive api interfaces are invaluable; everything else is a liability that is going to go stale.


Covering every case provides value that covering just one does not. Natural language communicates in a way code does not. The tradeoffs are a bigger topic than I feel like arguing about in this thread.

(I agree with all you said except the last clause.)


What is the expected behavior of, say,

  multiplex('Tom', 0)
or

  multiplex(null, 2)
?


Well, if the function was designed by me, then the completely obvious answers are '' and a NullPointerException. Anything else is bug-prone — if the first case does something different, then nearly every caller of the function will need to check to see if the count is zero, or it will get behavior for that case that is not correct for its purposes; and if the second case does something different, then the function is choosing to provide incorrect output instead of crashing.

Also, I would have called it something different.

However, over the years, I've learned that many things that are obvious to me are not, in fact, true. So, what are the arguments for any other possible behavior?


Backward compatibility could be one such factor.

In any case, documenting edge conditions helps in code maintenance.


Since I'm coming from Python, JS, and C, passing a null instead of a string doesn't seem like an "edge condition"; it's the same kind of error as passing an integer or Map instead of a string. Passing 0, I suppose, is an edge condition.


Even the Agile Manifesto itself explicitly says that its signatories value comprehensive documentation. They just value working software even more.


I had written a really long response here, drawing on my experience as a software tester.

I've deleted my post and have decided to argue my point in another fashion. Please enlighten me to the meaning foo(), here is the documentation:

    tests = [
       #Format: [InputA, InputB, InputC, InputD, Output1, Output2]
       [1, 2, 3, 4, 12,  -7 ],
       [2, 3, 4, 5, 27,  -14],
       [3, 4, 5, 6, 48,  -23],
       [4, 5, 6, 7, 75,  -34],
       [5, 6, 7, 8, 108, -47],
       [6, 7, 8, 9, 147, -62],
    ]

    for a, b, c, d, o1, o2 in tests:
        failUnlessEqual((o1, o2), foo(a, b, c, d))

Can you tell me what foo() does please? Its a ridiculously simple function. I work with tests like this quite often. It doesn't confuse me though, I like to put comments in my code and even the test code, but you don't need those.


Would any amount of written documentation or comment make foo(a,b,c,d) any less of a hopeless mystery in use?

Yes, a poorly written test for a horribly named function with impenetrable argument names make for a shitty experience for the api user, but is that really insightful? Hopelessly meaningless method names with impenetrable arguments would be just as shitty to use if they came with a page-and-a-half of prose.

And if the hypothetical idiot who wrote your hypothetical test-as-documentation is the same idiot who would instead by providing some other form of written documentation, why would you expect it to be any more clear?


Thats not a documenting test though, thats good choice of names. You don't need tests to have a good choice of names. If I said:

    slope, yintercept = calculateLine(x1, y2, x2, y2)
You wouldn't be using the test as documentation at all. Those names are so goddamn good you don't even need documentation. You would just be using the interface, and basic maths knowledge. (Note: it doesn't actually do that, I just thought of that as something that fits the argument/output count)


That's not a particularly good choice of names, actually:

> slope, yintercept = calculateLine(x1, y2, x2, y2)

It is, in fact, a place where a test would be quite helpful. A test might have prevented the typo for the second argument to calculateLine().


>A test might have prevented the typo for the second argument to calculateLine().

If the compiler didn't puke on it in the definition, or if that was the line in the test it would just be a bug in the test. Of course that all depends on the time honored development tradition of running a hastily written online comment in production. As far as the assumption I'm knocking tests for their utility at testing, I'm not, I'm knocking their utility as documentation.


> As far as the assumption I'm knocking tests for their utility at testing, I'm not, I'm knocking their utility as documentation.

Except you're not doing that, either. You're knocking the utility of a poorly-written tests-as-documentation test versus the utility of some hypothetically better written documentation.

But if your developers are going to write such uninformative tests-as-documentation, there's no reason to believe their documentation-as-documentation would be any better, so all you're really doing is making the uncontroversial assertion that poorly written documentation sucks.

Making the more relevant and informative comparison of well-written documentation-as-documentation to well-written tests-as-documentation, the tests-as-documentation have the inescapable bonus of being incapable of falling out of sync with the code.

In my, and a lot of other people's, experience, textual documentation tends to become an outdated liability almost as soon as it is written.


Nope, I am not going to bother. Your function has a terrible name, and the names of the arguments are not available to me. Had you given it a good name, and listed its arguments, I bet that the test would be quite a nice example of how to use it.


But my documentation runs during the hourly and never raises any red flags! That means its right, therefore better than a comment.

edit: You did kind of prove my point by asking for documentation outside of the test. If this "documentation" test isn't enough to find out whats really going on in my 1-line function, what makes you think a test and the best-chosen names in the world would give you any insight to a 2-line function?


> But my documentation runs during the hourly and never raises any red flags! That means its right, therefore better than a comment.

Your words, not mine.


Examples are great documentation. Python's doctest automatically tests them ensuring they still work.


I think that a good code talk for itself. Explain a code sounds like to explain a joke.


thats all good for one line functions. When you start writing functions with more than 5 lines....


One thing my supervising professor has prodded me to do is to begin keeping a working journal. Nothing fancy, just a plaintext file with date markings.

I've heard the idea before and dismissed it, but it's surprising now how often I will go back and check that journal for why I did something.

I already try to write informative git log entries, but the journal really lends itself to long form exposition.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: