19 ms·
Practical Go: Real-world advice for writing maintainable Go programs
- briatx 8y ago> Naming the Config parameter config is redundant. We know its a Config, it says so right there. > In this case consider conf or maybe c will do if the lifetime of the variable is short enough. This seems petty. Is it really that problematic to type out a few extra characters?
- akerl_ 8y agoThe issue w/ calling it "config" is that you end up with the confusing scenario where "config" is the object and "Config" is the type, differing only in casing.
- pcwalton 8y agoWhy is that confusing? Local variables are, idiomatically, never capitalized in Go, so the distinction is obvious at a glance.
- rat9988 8y agoAs are unexported objects.
- ben0x539 8y agoYeah, getting to idiomatically capitalize names has been pressure to move them to their own package in the past, which feels awkward.
- akerl_ 8y agoSorry. To clarify: it's not that I'm confused by the distinction between capitalized vs uncapitalized, it's that visually, "config" and "Config" look quite similar at a glance, whereas "c" and "Config" are clearly visually distinct.
- bpicolo 8y agoThis is a fairly go-specific issue due to its unexported vs exported convention. Many languages solve this through case conventions
- sythe2o0 8y agoThe argument is that it is not fundamentally better to use the longer name in the given context, so why make it longer? I'd say it isn't about how long the variable takes to type either, but how long the code takes to read and parse.
- networkimprov 8y agoAnd this is worse advice: > Functions should do one thing only. ... In addition to be easier to comprehend, smaller functions are easier to test in isolation, and now you’ve isolated the orthogonal code into its own function, its name may be all the documentation required. Using single-caller functions as a substitute for comments makes the workings of a specific operation much harder to follow, as you have to jump around the source to understand its effects. A long function is easier to understand than an exploded one. Also tests should target specific operations (aka functional tests), not every single function in the program. EDIT: Every function you add becomes part of your internal API. Any API, exported or not, should comprise a cohesive collection.
- ragona 8y ago> A long function is easier to understand than an exploded one. This is a pretty controversial position, and quite situational in my opinion. I absolutely agree that having to hop all over the source to understand something is frustrating, but that doesn't mean that very long functions are the right solution. Some combination of reasonably named helper methods and a function flow that makes the logic easy to parse should be the goal; either end of the spectrum is a problem.
- networkimprov 8y agoI took issue with this because it's a conventional wisdom, and does a fair bit of damage. Single-caller functions attract other callers over time, gain backwards-incompatible features, and result in regressions.
- Scramblejams 8y agoThis is one reason I like nested functions. They’re not available to the surrounding scope so they don’t succumb to these weaknesses, while also allowing you to organize your very long function internally by task. I use ‘em in Python all the time. It’s a bummer that more languages don’t support them, though you can get there with lambdas too, sometimes at the cost of more syntax.
- tdb7893 8y agoI'm wrestling with this at work right now and the short names don't really bother me as they are right in that the fact that if you have the type the shorter name can make the code easier to read slightly. What annoyed me is that for those single letter names I always got collisions so my naming was inconsistent. I personally ended up doing medium length names (like conf) except for the case where there was only 1 local variable (and sometimes if there were two or three) because in that case there were no collisions and what that variable was is very very clear
- pcwalton 8y agoYeah, I used to go by this advice, and I found the maintainability of my code dramatically increased when I typed out full names. I don't even use "i" for loop variables anymore. If the length is a problem, invest in an editor with autocomplete. Well-known abbreviations are fine, like "iter" and "prev", but single-letter variable names notoriously impede readability for me.
- dilap 8y agoI feel like I have the opposite problem. If the name is more than a few characters long, it starts to become non-instantaneous to recognize it. Things get much easier to follow with visually-instantly-recognizable symbols. So in conditions where a variable is used over a short area in the code (or where it's used _constantly_ over a wide area), I prefer short variables.
- hackinthebochs 8y agoTotally agree. If you have to scroll up to be reminded what some variable means, a longer name is good. But if its usage is a few lines away from its declaration then there's no reason to add visual noise to your code.
- shaan7 8y agoIts easier said than done though. We started following this few years back but then while it was "few lines away" originally, code evolved and now there are parts where its 20+ lines away. This means that short var names need to be continuously "enlarged". Well, then why not start with a "medium" name and avoid all that headache? ctx is a good enough compromise between c and context. (c can be client, config, context, certificate ... you get the point).
- Cthulhu_ 8y agoWell as the article states, one-lettered variable names should only really be used in tight loops; `ctx` is a better name for e.g. a function argument.
- deleted 8y ago[deleted]
- ilovecaching 8y agoIt says so in the signature, but Go code tends to have long functions, so it's kind of a fallacy to say that it's going to be easily recognizable 50 lines in because it's in the signature. Now if this were Haskell and it was a single small expression, it would be different.
- karmakaze 8y agoAlso in this case Go makes you use types in the name since it doesn't allow function overloading.
- omeid2 8y agoIt can be a problem with Config comes from package `config`.
- zerr 8y agoThey don't use any IDEs or modern editors, but vim as a bare bones text entry, without syntax highlighting or word completion.
- sacado2 8y agoIt is problematic for readability reasons as long as you are making complex expressions: configuration := NewConfiguration() configuration[word] = parameters[values[line][column]] - parameters[values[column][line]] vs conf := NewConfig() conf[w] = params[vals[i][j]] - params[vals[j][i]] It takes your brain more time to parse the first line. Now, there is an obvious limit, this is probably too much c[w] = p[v[i][j]] - p[v[j][i]] unless maybe the scope of the vars is very limited.
- Casperin 8y agoThe first version was too long to fit on the screen for me. Reading the second I missed the swapping of `i` and `j` -- only noticing it when I went back to the first version to figure out which parts of line I would assign to something if I were to rewrite the code.
- dagw 8y agoIn your example I would compromise with: conf[w] = params[vals[line][column]] - params[vals[column][line]] or even: conf[w] = params[vals[l][c]] - params[vals[c][l]] if you really want to save a couple of characters. Confusing which loop index is indexing which dimension is far too easy.
- deleted 8y ago[deleted]
- amedvednikov 8y agoGreat article, as always, by Dave Cheney. I took a lot of his advice when designing V [1]. It's very similar to Go, but it has - No global state - Only one declaration style (a := 0) - No null - No undefined values - No err != nil checks (replaced by option types) - Immutability by default - Much stricter vfmt - No runtime - Cheaper interfaces without dynamic dispatch [1] http://vlang.io http://vlang.io
- axaxs 8y agoJust wanted to say...as both a language nerd and opinionated snob, I'm really excited by what I see with V thus far. I'll be following your progress for sure and wish you the best.
- amedvednikov 8y agoThanks!
- stouset 8y agoThis seems to address basically all of my frustrations with go. Well done!
- SaulOfTheJungle 8y ago> - No err != nil checks (replaced by option types) Where can I find code illustrating V Option types?
- amedvednikov 8y agohttps://github.com/vlang-io/V/blob/master/examples/users.v https://github.com/vlang-io/V/blob/master/examples/users.v They are very simple and combine Rust's Option<T> and Result<T>.
- Sholmesy 8y ago// `http.get()` returns an optional string. // V optionals combine the features of Rust's Option<T> and Result<T>. // We must unwrap all optionals with `or`, otherwise V will complain. s := http.get(API_URL) or { // `err` is a reserved variable (not a global) that // contains an error message if there is one eprintln('Failed to fetch "users.json": $err') // `or` blocks must end with `return`, `break`, or `continue` return } This looks really handy. Is there somewhere else (outside of V) where I can read about Option types (in the real world, or in theory).
- OutsmartDan 8y agoWould love to see better separation on the page between "Bad" and "Good" examples. While a good read, it's difficult to quickly distinguish between the two. For example: https://github.com/airbnb/javascript https://github.com/airbnb/javascript
- andriosr 8y agotrue. Google will index it and I can see many people copy and paste bad code considering the best practice for doing something
- letientai299 8y agoCame from Java world, I find the Go comment and godoc are really limited. We can't link between functions, types. We don't have a standard way to declare input, output, don't have any distinction between a normal word and a Go identifier Refactoring using tool (e.g: Goland) usually lead to unexpected text replacements. Take following functions, for example: // Auth check if user credential is existed in bla, bla... // ... (many more explanation) Auth(user User, authProvider AuthProvider) bool // AuthDefault check user against the default **AuthProvider** AuthDefault(user User) bool { return Auth(user, new(DefaultAuthProvider)) } If only the AuthProvider in the second function's godoc be a link to the first one, we don't have to repeat the explanation for the second one. Dev will be able to discover the explanation easily via their IDE. This alone will be very helpful for the maintainability of any big projects.
- TheJazi13 8y agoI’d argue there’s a failure of the code to some extent to need such extsenibility with documentation. I’ve often found when I need more info outside of Godocs that simply clicking through and diving into the source provided me with all the answers I needed to know.
- michaelcampbell 8y agoYeah, every time I need to know why my car isn't working correctly, I take it apart. All the answers I could possibly want.
- q3k 8y agoFixing a car in everyday situations is a false analogy to writing code or using library code.
- mrbrowning 8y agoThis attitude, which I'll paraphrase as "needing any feature that I personally consider unnecessary is a code smell," is endemic to the Go community and a big reason why I still find myself frustrated by it on a daily basis even after a month of using it for a greenfield project. I don't like the language itself, since I'm generally in favor of a language offering more affordances rather than fewer, but it's fine to write in and I'm ultimately concerned with making the pragmatic choice. When I go to look for what the idiomatic way to do something is, though, wow! It's rarely just "this is the way to do it, since the language optimized for a particular use case by making the tradeoff of omitting the usual features for this": instead, it frequently goes on to assert that those features are unnecessary in nearly every case and languages that provide them are wrong, or old-fashioned, or too Academic, or the people that use them don't care about Getting Things Done. Also, throw in a few cargo-culted potshots at Java for good measure! I've used a good number of languages professionally at this point, and the Go orthodoxy is easily the most off-putting I've ever encountered. I don't know why it's become so aggressively totalizing, but it's not a good thing.
- deleted 8y ago[deleted]
- holografix 8y agoNice write up, could use a little simple editing / spell check!
- arendtio 8y ago> A good Go package should strive to have a low degree of source level coupling such that, as the project grows, changes to one package do not cascade across the code-base. I wonder though why there is so little emphasis on how important interfaces are in Go. I mean, section 4.5 talks about it a bit, but in my experience, this mistake is made far too often.
- entity345 8y agoThis is encapsulation, really, and simply makes the point that the principle applies to packages.
- jstewartmobile 8y agoLove Mr. Cheney, hate stuff like this. Someone who has put the hours in will probably stray from all of this advice, and still end up with something beautiful. Meanwhile, for all of the other schmoes who haven't put the hours in, this is just more fuel for screeching about how this name isn't right or that comment is too long. Dijkstra said GOTO was bad, and now we have callback hell and 10-layer inheritance hierarchies instead.
- AnimalMuppet 8y agoIf you're writing the kind of thing where you need callbacks, GOTO isn't going to make it more clear what's going on...
- jstewartmobile 8y agoNever said it would. I did imply that things haven't significantly improved. Throw away goto and globals, replace them with anonymous functions and closures, and the new thing looks an awful damn lot like the old thing. All I know is that we used to have retrained housewives writing physics programs. Now we expect 10,000 hours to write a halfway decent web form, and halfway decent doesn't happen nearly as often as it should.
- EngineerBetter 8y agoReally glad to see more pragmatism and less dogma in the Golang community.
- bsaul 8y agoThis has got to be one of the best and most useful post i’ve seen about go (and code design in general) in a long time.
- deleted 8y ago[deleted]
- gigatexal 8y agoThis is so fortuitous as I started writing my first real Golang service and as a python dev I have no idea what I am doing. But one refreshing thing is how opinionated the language and the frameworks are refreshing as there’s only one acceptable way to do many things.
- hashhar 8y agoI agree so much with this. I recently attended a Golang meet up and since I was one of the few who had been using the language regularly for over 2 years now i was asked to let the newcomers know my favorite "feature". I replied with the same point you make above and was met with a look of refreshment on people's faces. Go lets you write One True Code for the most part.
- everybodyknows 8y agoI have found this advice spot-on for more subtle questions, over my last two years of learning and building with Go full time: https://golang.org/doc/effective_go.html https://golang.org/doc/effective_go.html Also see back issues of the Go team blog for insight into concrete implementation realities of slices, interface types, GC and more.
- doodpants 8y agoI'm not a Go user myself, but I skimmed through the document. What I found was a lot of good programming advice that is generally applicable, rather than being limited to the Go language. (It seems most of the Go-specific stuff is in section 5.) It's always good to see robust coding practices being promoted, regardless of language.
- argd678 8y ago> 2.1. Choose identifiers for clarity, not brevity This is a real problem in Go these days, people use one and two letter vars quite a bit which makes reading code you’re not familiar with practically impossible. On one project we simply switched to semi-java length like names since our customers couldn’t read the code.
- entity345 8y agoSince when are variables names a language issue? There is no such thing as "Java length like names", or whatever. There are good practices and bad practices and they are universal.
- noir_lord 8y agoThey usually aren't however they can be an issue if the idiomatic style trends towards towards bad ones.
- futureastronaut 8y agoDogmatism and Go are peas in a pod. If your variable is longer than one character you're a dirty Java programmer, or something to that effect.
- croshan 8y agoIt's really more of a language community issue, but communities of a language are commonly referred to by the name of the language itself.
- twentythree 8y ago> 2.3. Don’t name your variables for their types What's the best way to handle a situation where you use two different types for the same data? e.g. var usersMap map[string]*User var usersList []*User
- jpttsn 8y agoI’d call the former usersByID (if that’s what the string is)
- samstokes 8y agoHow about describing the intended use of the collections? E.g. var usersByUsername map[string]*User var usersToEmail []*User
- vorg 8y agoYou could use a struct with private fields, e.g. type Users struct{ dict map[string]*User list []*User } and attach methods to both access the data and coordinatedly update both forms, e.g. func (us *Users) byID(id string) *User { ... } func (us *Users) sortedByID() []*User { ... } func (us *Users) addUser(id string, nu *User) { dict[id] = nu list = append(list, nu) ... }
- jimsmart 8y agoPersonally I'd use usersByID and users - I nearly always name my maps thingsByKey, and my default is that a plural thing on its own is nearly always a list, so adding a suffix of List doesn't add anything much here for me (Cheney's "Don’t name your variables for their types").
- diegs 8y agoAbout half-way through and I think this is a great article, in particular the quotes and I also agree that the first 4 sections are generally applicable. One thing I disagree with is the remark about having fewer, big packages. Though conceptually I agree that avoiding having too many public APIs that aren't widely used makes sense, in practice--at least on the types of projects I tend to work on--I find that directing people to split things into a few packages forces them to think about a decoupled design with good APIs between the components. This could certainly be done with discipline inside a single package, but unless everyone working on the codebase is very diligent about this it's easy for abstraction leaks to creep in. Ultimately it's a judgment call, but I think an earlier paragraph (copied below) is far more important than optimizing on having fewer packages or fewer exported types and functions, especially (as is also pointed out in the doc) you can use `internal` subdirectories to make APIs project-private if you are writing a library that is consumed by other projects, as opposed a service. > A good Go package should strive to have a low degree of source level coupling such that, as the project grows, changes to one package do not cascade across the code-base. These stop-the-world refactorings place a hard limit on the rate of change in a code base and thus the productivity of the members working in that code-base.
- jerf 8y agoI agree with you. I like the way packages are isolated from each other, meaning that to understand a package it is usually by definition a good start to simply read what is there. Smaller packages mean more bite-sized chunks of the program. And I think the discipline of slicing up your program this way is very, very good for the design, and often makes the tests easier to write to by significantly shrinking the surface of what your tests have to "fake" in order to test your code. I think it's just a whole heapin' helpin' o' benefits. However, I feel myself to be in the minority on this one. To which I basically shrug and write my code with lots of relatively small packages. It really only affects code you're working on, or that your team is working on. Things you pull in as libraries and have no direct interaction with don't matter too much on this front.
- bennofs 8y agoIf packages are too small it can get hard to understand the code if your not familiar with the structure yet. Working from bottom to top level can work but might also be different because you are missing context for the low level packages to make sense. In general I found larger packages tend to produce more direct / pragmatic code with less indirection which is usually easier to understand, even though it also feels wrong to me from a theoretical standpoint.
- krat0sprakhar 8y agoSlightly offtopic: Does anyone know which template was used to generate this document? I'm assuming the source in markdown and I really like that the generated html has table of contents, support for footnotes and is responsive!
- andrenth 8y agoIt’s asciidoctor, and it’s indeed really nice. https://asciidoctor.org/docs/user-manual/ https://asciidoctor.org/docs/user-manual/
- tomcatfish 8y agoI can't offer a solution to what it was made with, but the italicization on this paragraph makes me almost sure it was a markdown source. The go tool also supports a special package declaration, ending in test, ie., package http_test. This allows your test files to live alongside your code in the same package, however when those tests are compiled they are not part of your package’s code, they live in their own package. This allows you to write your tests as if you were another package calling into your code. This is known as an _external test.
- deleted 8y ago[deleted]
- dana321 8y agoMy advice would be this: If you have a specific reusable component.. Make a package to contain it. Use init() to compile any regular expressions and store them as variables within that package, so that you don't Split out related code in a folder, remembering that the included code is done in alphabetical order - so shared functions within the same package you should alphabetically make it the first to be included. You can register components of a larger system by using the init() function to call a function in the base package, and within the calling package (usually main) import the "plugin" using the underscore space prefix (sql database drivers use this method) Use the new go modules system; its great. But sometimes it doesn't include the latest packages, just modify the go.mod file and anything after the package name remove it and just put master instead. One last thing with error handling. Everybody does if err!=nil. I tend to go the opposite way, if err==nil and nest these, if err isn't nil i drop out and deal with the error. If it is ok, it will return ok.
- deleted 8y ago[deleted]
- undreren 8y ago> One last thing with error handling. Everybody does if err!=nil. I tend to go the opposite way, if err==nil and nest these, if err isn't nil i drop out and deal with the error. If it is ok, it will return ok. Don't you get "staircase of doom"? err := unsafe1() if err == nil { err = unsafe2() if err == nil { err = unsafe3() if err == nil { err = unsafe4() if err == nil { err = unsafe5() if err == nil { // ... } } } } }
- jimsmart 8y ago> Use init() to compile any regular expressions and store them as variables within that package No need to resort to init() for regexps, simply use an unexported package level variable: var validFoo = regexp.MustCompile(...)
- tschellenbach 8y agoThis book is a great read for writing maintainable code: https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882/ref=sr_1_2?s=books&ie=UTF8&qid=1550785898&sr=1-2&keywords=beautiful+code https://www.amazon.com/Clean-Code-Handbook-Software-Craftsma...
- johnisgood 8y agoI don't understand something. > 5.1. Consider fewer, larger packages > 5.2. Keep package main small as small as possible If you have one package, which is the main package, then how are you supposed to keep it small, and at the same time not creating more packages because somehow that is the work of the devil? He is telling me to try to fit everything into one package, but at the same time keep it really small. It is not going to work. Maybe I misunderstood?
- hactually 8y agoYour main package can sit under `cmd/foo/*.go` and import a few larger packages That's what it means.
- dub 8y agoIn golang packages named "main" are special. They're not importable and they correspond to a single executable. If you want to be able to reuse code in different packages in the future or have more than one compiled binary, you won't be able to put everything into a single "main" package
- johnisgood 8y agoRight now I use many internal packages. It makes my code more organized. By many, I use 4-5 and the number probably is not going to change. I dunno if this is a good idea, but it seems like the best I have got that suits me.
- ilovecaching 8y agoSo I don't know how to fly a commercial airliner, but I could probably figure my way around a small single prop airplane. That's basically the difference between Go and a language like Rust or C++ or any language that requires a lot of up front investment, but then let's you work at power level 9000. So yeah, Go will get you there. It will take you a lot longer to get there when you aren't going 600MPH, and you can carry a lot less people, but at least you aren't driving a car like the people using Python. Sure it's probably not going to be as rigorously inspected as the Boeing borrow checker, but Go is at least getting an inspection, whereas your Python code is just going to break down on the side of the road when the problems surface itself because you didn't check the oil light. To me, Go is a better Python. It's easier to learn, faster, more scalable, and safer, and just as easy and quick to write. It's just not a language for big projects.
- platz 8y agoBut Go will never be a data science language like python, because Go is too limited to express that domain. Python may leave you in the lurch when it comes to maintainability, but for some domains python is infinitely better than Go.
- kkert 8y agoWhats wrong with Python maintainability ? As long as you CI and lint properly, i haven't seen much issues
- fastbmk 8y agoGo fanboys won't get it. They are too busy proving their language doesn't need Generics.
- ilovecaching 8y agoI agree with you, but data science is a separate field, so I don't normally factor it into my discussions of tools for software engineering.
- platz 8y ago
- deleted 8y ago[deleted]
- Groxx 8y agohttps://dave.cheney.net/practical-go/presentations/qcon-china.html#_never_start_a_goroutine_without_when_it_will_stop https://dave.cheney.net/practical-go/presentations/qcon-chin... > 8.3. Never start a goroutine without [knowing] when it will stop. 100% agreed with the concept, but even the final example is flawed. That'll unblock and continue immediately after `close(stop)`, without being able to do two important things: it can't tell you when it's done shutting down, and it can't tell you if it encountered an error. Fixing this makes it even more complex. Both of those are common and often necessary things to do - e.g. if you need to drain traffic, you need to wait until it's actually done before killing your process. Same goes for closing files (you might need to flush / sync first), OS resources that might survive your process, etc. This example will just kill your process as soon as everyone has been informed that it should stop, not when they're done. --- Yeah it's a bit nitpicky, but it's even called out as "asking a http.Server to shut down is a little involved, so I’ve spun that logic out into a helper function". Except that the helper function can't ensure the involved server shutdown process actually shut down the server. That's a dangerous pattern to encourage, and it's an over-simplification that's absolutely rampant in go tutorials and code I encounter. The workgroup lib he links doesn't (arguably?) have this flaw, but it has some strange / incomplete error handling details... E.g. if you Add 5 funcs, you'll only receive the error result of the first func to complete, not the first err or anything more predictable. If the first succeeds but all the rest fail, you'll see no error. Go's errgroup does "first non-nil error" at least, but you can still only get one: https://godoc.org/golang.org/x/sync/errgroup https://godoc.org/golang.org/x/sync/errgroup
- coder543 8y ago> 100% agreed with the concept, but even the final example is flawed. That'll unblock and continue immediately after `close(stop)`, without being able to do two important things: it can't tell you when it's done shutting down, and it can't tell you if it encountered an error. Fixing this makes it even more complex. The final example actually does both of these things already, but maybe you're confusing the purposes of some of the channels? The loop at the end of main will wait until it receives one (possibly nil) error value from every task that was launched. Upon receiving the first error, it will close the done channel, which will then cause any remaining tasks to run their Shutdown function, which will attempt to gracefully shutdown the server. The error of each shutdown function is then returned and printed if non-nil. There is no immediate termination, and all errors are communicated. It doesn't let the process die until every task has sent its error value. Obviously, there are two semantics not enforced at compile time: each task must send exactly one error value, and each task is responsible for its own shutdown. If a task sends more than one error, there will be early termination. If a task doesn't shut itself down properly, that is its own fault.
- tarasmatsyk 8y agoGood collection of best practices overall. I would not say they are Go-only, as I read about them 5 years ago in Clean Code by R. Martin and another part can be found in Code Complete by Steve McConnell. Once again, good collection if you have no time to read the book. Anyway, it does not cover other parts like contracts/interfaces, the absence of comments (which can be a good sign) etc. Would recommend checking both CC books if you want to write better and maintainable code. PS. Anyway, Dave, thank you for the popularization of best practices.
- fastbmk 8y ago> Coming from Java? If you’re coming from a Java background, consider this rule of thumb. - A Java package is equivalent to a single .go source file. Put a volume of a java package in one .go source file? Do all Go programming language advocates/evangelists have a mediocre intellectual level or just Dave Cheney?
- fastbmk 8y agoOne more advice - implement Generics in your programming language, it's 2019 already, not 1970s!
- leshow 8y agoI've always found Go's 'guiding principles' to be highly subjective, the annoying thing about it is that they are masqueraded as objectivity. Simplicity and readability for whom? If you've been writing C and Java for years, all simple/readable means is 'familiar'. There are other definitions of simplicity
- fastbmk 8y agoGo developers have a sectarian mind set. They obey the cult of The Three Creators, and some other minor advocates/evangelists like this australian dude.
- shoo 8y ago> That’s a lot of repetitive [error handling] work. But we can make it easier on ourselves by introducing a small wrapper type, errWriter. > errWriter fulfils the io.Writer contract so it can be used to wrap an existing io.Writer. errWriter passes writes through to its underlying writer until an error is detected. From that point on, it discards any writes and returns the previous error. this is a cute trick. In some cases it might produce a great result. For example, floating point nan values work roughly like this: a special error values included in the type that can be computed upon without needing to write special control flow until you explicitly need to check the final computed result to see if you got an actual value or the nan value. but I fear the applicability of this trick in go is limited as it relies on a heap of idiosyncracies belonging to this example: The chain of potentially error-ing operations that are being performed all involve a value of the same type that can be wrapped. In a less contrived example there would be a variety of different types involved in the sequence of possibly error-ing operations. The code that is processing the wrapped error-hiding type roughly must not perform side effects when processing an apparently non-erroring computation, since now the error wrapper type is stopping the code from aborting early. It's completely non obvious that this transformation will be correct (ie result in a program with equivalent behaviour to the original program) without reading exactly through the implementation of everything that touches the error wrapper. This isn't helped by go not having a way to tag functions as pure. The new code now superficially looks like it is wrong as it isn't bothering to check error values of the functions it is calling in the usual way. This will probably cause a spray of linter warnings about failing to handle possible errors in your CI build. In other languages a much more general way to address this kind of problem could be: error monads ; or exception handling Which can be used to short circuit the entire normal control flow.
- masklinn 8y ago> In other languages a much more general way to address this kind of problem could be: error monads ; or exception handling errWriter is quite literally "let's do monadic error handling, except not just without monads but without generics or reified errors". And thus it can only handle a single source or error type. Also FWIW monadic error handling != error monads. For instance Rust does the former, but its type system can't express the latter (so there is no monad or functor abstraction on top of Result).