Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Hey HN, I wrote this post. I'm getting the sense that not a lot of commenters are taking the time to read the contents before reacting to the title, so I'll share a little spoiler/TL;DR:

Good boilerplate is boilerplate that usually gets changed over time (and in different ways each time) as we iterate over the code.

It took me a while to come to this realization (and I explain the reasoning and consequences in the post), so I hope it's helpful to other people who might not have found this intuitively obvious.

Also perhaps "terse" or "clever" are not the best words to describe the counterpoint—I welcome edit suggestions.



Code is meant to be read and incidentally to be executed. Code also should be read more often than it's written.

With allegedly idiomatic Go:

    b := 0
    for _, r := range results {
        j, k := r[0], r[1]
        if !k || j % 2 != 0 {
            continue
        }
        b += j
    }
... there are eight lines to read and then the reader is forced to draw the conclusion of what the code does at a higher level:

    // I see ... given an iterable of results pairs, we just sum the
    // first elements that are even for the pairs in which
    // the second element is truthy!
With Python, you can write the exact same code that asks for the same mental gymnastics.

Python:

    b = 0
    for j, k in results:
        if (not k) or (j % 2 != 0):
            continue
        b += j

    # I see ... given an iterable of results pairs, we just sum the
    # first elements that are even for the pairs in which
    # the second element is truthy!
But it also allows for a more direct expression of intent by using a higher level construct:

    b = sum( ( j for (j, k) # ... sum the first elements 
               in results # ... of results pairs 
               if (k and j % 2 == 0) # ... if first elt is even and second elt is truthy
             ) )
The argument seems to be that "when reading and modifying code, certain lines may contain constructs that will require more attention to be paid to a line" is a very serious problem, the solution for which is to enforce that all lines are equal in that they do relatively little at the language level, which Blub (err.. Go) does.

It's further argued that lowest-level-constructs-available scaffolding that may occasionally need to be added in the moment while tinkering is worth keeping around forever despite any tax it imposes on the creation, the reading, and the ease of modification of the codebase and a great advantage of Blub (err... Go) is that it forces this to happen by only having such constructs in the first place.


As a non-Go/Python user, those two snippets look practically identical to me, although I understand just enough to have written the same comments. Unfortunately, the comments don't tell me what I'd really like to know about the code if I ran across it when debugging:

* What are b, j, and k?

* What's so significant about even numbers - or is 2 a magic number that could just as easily have been 3?

* What can I assume about results? Can it be empty, null, 10 elements, 10 million elements?

With proper naming and a good, brief chunk-level comment, I could tell at a glance whether this chunk is likely to be hosting my bug or not. 5 lines vs. 8 lines don't play much into it.

Actually, this really isn't what I think of when I hear "boilerplate". I think of, for example, the adapter pattern, or having to build up a finicky set of states every time some common task is done.


The first python snippet was meant to be identical to the Go one and the Go one is the one from the article.

> With Python, you can write the exact same code that asks for the same mental gymnastics.

The second Python snippet would idiomatically be written on one line and that's what the article is railing against for unclear reasons.

The comments would not appear in source code, they're supposed to illustrate the thought process of the reader as they encounter the source code.


As a heavy daytime user of Go, I kind of get what you're getting at. You gave as examples two of the most typical cases of Go boilerplatitis: Error handling and for loops. Of that, error handling definitely gets refactored to set states, wrap errors, do cleanup (though less often, since go has defer) etc.

I find myself refactoring Go code a lot in patterns just like you describe (internal functions for error handling), but for me it feels like a throwback to my imperative programming era of the 90s, using Pascal and C. I'm still old enough that it automatically clicks in my mind as clean code, but I also know it's hopelessly verbose.

Error handling doesn't have to be this verbose. In other static languages that eschew exceptions you could do something like this to achieve the same result:

  let result = try!(DoSomething());
This form is clearly more readable once you understand what try! does (which doesn't have a steeper learning curve than the weird if syntax in Go).

Now, can you refactor it without throwing away all that nice terseness? Sure. Let's say we need to wrap the error value:

  let result = try!(DoSomething().map_err(|e| MyError.wrap(e)));
It takes time to get used to, but you state what you're doing instead of how you're doing it.

Go's verbosity is even more cringing with the loops, and this is actually a place where I rarely need any refactoring in my experience. Is your experience any different? Can you give an example of you ended up refactoring loops?


I'm getting the sense that not a lot of commenters are taking the time to read the contents before reacting to the title

As evidenced by none of the discussion hitherto noticing that your Python and Go examples actually perform a slightly different process? Was that deliberate? ;-)

(I rarely use Python and never Go; yet I noticed quite easily that the Python code is summing the selected odd elements, while the Go code is summing the selected even elements.)


> As evidenced by none of the discussion hitherto noticing that your Python and Go examples actually perform a slightly different process? Was that deliberate? ;-)

Good catch. I like abstract, declarative and structured code because if nothing else, it requires some level of engagement to write which has a side effect of reducing silly mistakes.


Good catch indeed! Maybe I should keep it as-is for fun. :)

Edit: I changed it.


I would add, code that changes over time but not often enough to warrant a new abstraction.

If you're only using the boilerplate for a new project every few weeks, it's probably overkill to set up some abstraction with variables and templating language.

Granted, I'm using "boilerplate" here in a more general sense than you did in your blog post.




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

Search: