8 ms·
if err == nil { _, err := w.Write([]byte(g.Name)) if err == nil { err := binary.Write(w, binary.LittleEndian, g.Age) if
by nknighthb 13y ago
if err == nil {
_, err := w.Write([]byte(g.Name))
if err == nil {
err := binary.Write(w, binary.LittleEndian, g.Age)
if err == nil {
return binary.Write(w, binary.LittleEndian, g.FurColor)
}
return err
}
return err
}
Why does anyone have to tell people not to do this? How does it enter anyone's mind as a thing to do in the first place? I've been known to go too far to minimize nesting. I get twitchy at the second level. By the third my brain is trying to crawl out my eyes and strangle me, even on code that doesn't have a potential exit point at every level.
- dragonwriter 13y ago> Why does anyone have to tell people not to do this? How does it enter anyone's mind as a thing to do in the first place? Its pretty much the natural naive composition of conditionals. I think eventually most people come to the point where the rightward-marching blocks become irritating and they look for ways to avoid them, and in the case of conditionals where one branch ends in a return there is an easy way to do that, but that particular case (and, thus, the solution) may not be as familiar to people coming from languages where error returns aren't idiomatic (though you do run into it elsewhere, just not as often, so its less likely to result in deep nesting if you don't pay special attention to it.)
- ajanuary 13y agoI normally see a form of it from people that don't like early returns. But if you try to do that with code that explicitly passes pack errors, you end up having to twist the code to avoid early returns. I guess this form pushes all the returns into a list at the bottom, which makes them more comfortable?
- MetaCosm 13y agoPeople don't like early returns because sometimes they return before some basic cleanup / must have function component is run. Normally added by someone unfamiliar with the function accidentally. Go's defer statement makes this much less of an issue, you bind your work piece with your defer piece so that early returns are fine.
- btilly 13y agoHistorically some people were against early returns, loop flow control, and the like as part of structured programming's overreaction to the widespread unstructured use of goto for flow of control. They wanted every body of code, be it a function or a loop, to have one entry point and one exit point. Over time programmers have learned that one entry point, multiple exit points, is OK. But I feel that it is a good habit to flag that with a comment in capital letters so that someone skimming the code can't miss it.
- LBarret 13y agoEven the correct version is not that good. The multiple checks on nil is an obvious pattern and as such should be abstracted away. Haskell does it with Maybe but as long as you have function as first class object, it should be doable. Like in Python, Ruby, etc.
- dragonwriter 13y ago> Even the correct version is not that good. The multiple checks on nil is an obvious pattern and as such should be abstracted away. From a pedagogical point of view, showing the first transition from the anti-pattern is a good practice, because once you learn that, those first transitions are composable. So, while I agree that the correction for this isn't the best end-state, its an appropriate way to show how to correct the nested-conditionals-with-error-returns anti-pattern.
- eckzow 13y agoThe very next slide cleans it up using a "utility type" which reminds me of the "monadic" Haskell solution.
- sanderjd 13y agoAbsolutely, I'm going through some of my code now where I had thought of the first improvement as obvious but hadn't considered that second, much nicer solution in Go. It really is like making a custom Maybe type. It might be another pattern to be improved on to have tons of these sorts of custom types running around, though...
- RyanZAG 13y agoNo way, the next slide is the very definition of evil unmaintainable code. Go through what is happening quickly: bw := &binWriter{w: w} bw.Write(int32(len(g.Name))) bw.Write([]byte(g.Name)) bw.Write(g.Age) bw.Write(g.FurColor) return bw.err If an error happens on L2, we will still run writes L3-L5. Why is this bad? Because in the future, we might come in and add logic after the writes complete. We have to make sure to do a check against the error or we will run this logic even if the write did not work. This is an incredibly easy trap to fall into. I'd go as far as to call that an anti-pattern - you are hiding the control flow in non-obvious ways.
- tptacek 13y agoI do it in C code instinctively; it's very useful for instrumentation, debugging, and resource management in straight C to have a single return point. When I get too far to the right, that's a signal that it's time to further decompose my functions; that signal is also useful, which is another thing that keeps me doing it. But that style doesn't make much sense in Golang. It makes even less sense in Ruby and Python, which have structured exception handling; if you're coming from Pythonistan, the idea of nesting conditions is probably entirely alien.
- cinger 13y agodo you know of any languages that have a sort of inverted switch statement to clean up a list of if statements like this? something like: invswitch err !=nil { case 'err := binary.Write(w, binary.LittleEndian, int32(len(g.Name)))': return err case _, err = w.Write([]byte(g.Name)): return err case err = binary.Write(w, binary.LittleEndian, g.Age): return err default: return binary.Write(w, binary.LittleEndian, g.FurColor) }
- stass 13y agoYes, CPS is essentially that and available in any modern language, or can be emuated by setcontext(2)/getcontext(2). However, it can become a mess very quickly. Another option is monadic style, which will pass error checking along. I believe it will work well for this example, and can be implemented in Go, most likely (I don't know Go, bur it seems so). Of course, the real problem with this code is that it is not decomposed properly. Nested error checking is the first sign of it, as somebody else already rightfully noted in the thread.
- pcwalton 13y agoI think you'd probably need generics to implement monadic chaining in Go.
- pcwalton 13y agoThis is similar to what Haskell do-notation provides.
- mrweasel 13y ago>How does it enter anyone's mind as a thing to do in the first place? Because a lot of people make up code as they go a long. Think of it as story telling vs. ordering. When story telling something happens and the your characters/code react to the outcome of that event, but stays in the same context. Instead it would be better to write code as your being ordered around by an old drill sergeant. He'll always tell you to do one thing and one thing only. When you've completed that task, you given the next instruction and are no longer in the context of the previous action. It's something I thought about often when reading code, both that of others and my own and it's the best explanation I've been able to come up with.
- lazyjones 13y agoPeople end up doing this because code gets written incrementally and often we start out wrong (e.g. here with the wrong/inverted condition). Rewriting large code blocks for a little more clarity is often a PITA. It would help very much if editors supported this better (e.g. single keystroke inversion of an "if"). Go with its easy syntax would be a particularly good target for automatic rewrites for code like the above, or even just editor-level hints for better style.
- mateuszf 13y agoYou can do that in Intellij Idea and related tools.
- copx 13y agoQuite a few people write deeply nested code for some reason unless you tell them not to. Maybe it depends on how your brain works. There is surprising variance within the human population. I find deeply nested code ugly and unreadable, others nest eight levels deep and love it. Some people even claim they find parentheses soup (LISP-like syntax) readable. Personally, I cannot comprehend that.
- James_Duval 13y agoI find Lisp-like syntax highly readable - more readable than C-like syntax for sure. I find that anything more verbose (EDIT: I'm having difficulty thinking of the right word for what I mean here - hopefully you understand anyway) is useful while you're writing the code, but actually increases the difficulty and length of time to understand what's going on when you come back to it. Symbolic representations of relationships - including parentheses among many others - just make more instant sense to me. To the extent that a characterisation of the Lisp family as "parenthesis soup" just does not make sense to me -- it implies an arbitrary jumble of symbols, while for me, as none of the parentheses could possibly be moved, they're in a perfect and immovable pattern. I'll admit, though, that without [ and ] I would sometimes be more than a little lost. I think I might enjoy an APL-like language if I ever had time to learn it...
- EdiX 13y ago> Some people even claim they find parentheses soup (LISP-like syntax) readable. Personally, I cannot comprehend that. Lisp is read like python, you read indentation, ignore the parenthesis.
- brianberns 13y agoBecause the alternative is worse. If you test-and-return after every call, your function has multiple exit points and is far less maintainable. If you nest like this, you at least have a chance of maintaining a single exit point in your function (even though this example fails to do so). This is why the Lord invented exceptions, which it seems that Go does not use. This one example is enough to convince me to never use Go for anything. What a huge step backwards.
- AYBABTME 13y agoI encounter this argument daily and I believe it usually comes from programming dogma of people who read very assertive quotes from Dijkstra. Most of the time when error checking, returns (or in C, gotos) are fine and lead to more readable code that's easy to make sense of and easy to step through with a debugger. Please don't fall into the trap of believing that I am terribly dogmatic about [the go to statement]. I have the uncomfortable feeling that others are making a religion out of it, as if the conceptual problems of programming could be solved by a simple trick, by a simple form of coding discipline! - Dijkstra (1973) in personal communication to Donald Knuth , quoted in Knuth's "Structured Programming with go to Statements" https://en.wikiquote.org/wiki/Edsger_W._Dijkstra https://en.wikiquote.org/wiki/Edsger_W._Dijkstra
- gnaritas 13y ago> your function has multiple exit points That's a good thing. > and is far less maintainable I'd say it's more maintainable. > you at least have a chance of maintaining a single exit point in your function Maintaining a single exit point is IMHO an anti-pattern; it makes ugly unmaintainable deeply nested code.
- krichman 13y agoI always use a single exit point in my functions and also a goto to get there from the middle.
- EdiX 13y ago> If you test-and-return after every call, your function has multiple exit points and is far less maintainable. There is nothing wrong with multiple exit points as long as: a. the language has a mechanism for scoped resource allocation (ie. defer, finally, with, unwind-protect or "RAII") b. the early exit doesn't happen in the middle of a long and complex function
- lmm 13y agoShort circuit returns are the devil - they make it much harder to factor out part of a function into a smaller function. A function should have one entry point and one exit point; that's the whole point of structured programming. If you're going to return from some random point in the middle of your function you might as well be using goto. (Of course, good programming languages provide a better solution than pyramid-of-doom nesting)
- nknighthb 13y ago> Short circuit returns are the devil vi! Naïve, absolutist positions in areas of long-standing debate between programmers of great experience and the highest imaginable competence just makes you look ridiculous.
- jemfinch 13y agoNaive, absolutist positions in areas of long-standing consensus between programmers of great experience and the highest imaginable competence makes one look even more ridiculous. By and large, the best programmers eschew nesting in favor of early returns. Invariably (in my experience) those who argue against early returns are inferior programmers (and not only by virtue of lacking taste in this particular debate).
- gnaritas 13y ago> By and large, the best programmers eschew nesting in favor of early returns. Yup. > Invariably (in my experience) those who argue against early returns are inferior programmers (and not only by virtue of lacking taste in this particular debate). Yup.
- stass 13y agoWhere did you get that idea of consensus? A lot of languages do not even have a return statement, neither does lambda calculus. Furthermore, CS community has long abandoned statement based languages in favor of expressions and relations which do not feature "return" for onvious reasons in forms other than equalent to jump.