4 ms·
In practice, scoping becomes an issue because "err" sneaks in everywhere. For example, consider: foo, err := getFoo() Now your scope has an "err" and you o
by lobster_johnson 9y ago
In practice, scoping becomes an issue because "err" sneaks in everywhere.
For example, consider:
foo, err := getFoo()
Now your scope has an "err" and you open yourself to logic errors such as:
foo, err := getFoo()
... do stuff ...
if errr := makeBug(); errr != nil {
return err
}
Spot the (heh) error? It's caused by ":=" allowing variables to be reassigned.
You can avoid this dangerous pollution, but it's super ugly:
var foo *Foo
if f, err := getFoo(); err == nil {
foo = f
} else {
return err
}
(You have to have the return in the second branch, otherwise "go vet" will complain. Sigh.)
I have lots of these, and they're not helped by the fact that Go has no type-level mechanism to guard against the misuse of multiple-return values. That is, in the above, either "foo" or "err" are usually set, and if "err" is set, it means "foo" isn't. But the type system doesn't prevent you from accidentally ignoring "err" and using "foo", or vice versus. This gets even worse if the func returns a (non-pointer) struct as its value.
The fact that Go allows shadowing (exacerbated by the convention of using "err" as every single error variable) and promotes the reuse of variables (e.g. the above ":=", or declaring "err" outside a loop) also means it's super easy to assign the wrong thing.
All of the above are my single biggest source of bugs during development. That, and how accidentally using nil/zero values, including the damnable nil map/slice cases.
"go vet" (whose shadowing check is disabled by default (!)) and the various linters only go some ways towards helping here. The errcheck linter (which you can enable through gometalinter) is absolutely essential.
- lowmagnet 9y agovar foo *Foo if foo, err := getFoo(); err == nil { return nil, err } // foo is usable now
- lobster_johnson 9y agoI suspect you don't know how := works. ./test.go:14:15: foo declared and not used
- flip_rect_red 9y agoit appeared to be a typo, and as you suggest the compiler caught it var foo *Foo var err error if foo, err = getFoo(); err != nil { return nil, err } //foo is usable now
- lobster_johnson 9y agoThat solves nothing. The point was that you pollute the scope with error variables, leading to dangerous logic errors that can be hard to spot. Your snippet is no better than: foo, err := getFoo() if err != nil { return nil, err } "err" still ends up in the scope.
- xenoncross 9y agoThere are real problems, and not real problems. I'd classify this as a not real problem. How hard is it to name errors differently when it would be a real problem? A few keystrokes.. and BOOM! Problem solved. I wonder what is the correlation between volume of complaining about minutia such as this compared to real life ability and level of accomplishment.
- lobster_johnson 9y agoWhen you work with Go every day (which I've done for last two years or so), small problems become significant annoyances. It's a decent language, but it's also a deeply flawed one. I could mention many other, bigger flaws, but this particular discussion was about errors. Also, it's actually okay to debate minutia. Dismissing something as "not real problems" is, well, dismissive.
- Xeoncross 9y agoWhat in the world? xenoncross?
- Xeoncross 9y agoYou almost always defined your variables. None of this `foo, err := getFoo()` nonsense. Most functions return an error so err is already defined: func doIt(a, b) (result string, err error) { ... } Then you are free to reuse err as needed. func doIt(a, b int) (result string, err error) { var x, y string x, err = doA(a) if err != nil { return } y, err = doB(b) if err != nil { return } result = x + y return }