4 ms·
> Its a significant, real problem. It's a contrived problem, at least. In reality, your code is going to look more like: func GetUserByID(id string) (stri
by randomdata 3y ago
> Its a significant, real problem.
It's a contrived problem, at least. In reality, your code is going to look more like:
func GetUserByID(id string) (string, error) {
s, err := QueryDatabase(fmt.Sprintf("select * from users where id = %v", id))
if err != nil {
// Do something with the error, returning a new error if necessary.
return "", someNewErr
}
return s, nil
}
The callers of GetUserByID have absolutely no concern for the implementation details of QueryDatabase. When requirements change and you replace QueryDatabase with QueryWebService, callers expect to still get the same errors back, not HTTP errors all of a sudden when the code was previously getting MySQL errors. That would be plain horrible API design. So, you wouldn't actually ever encounter this particular issue in practice (assuming nobody hates you).
- 015a 3y agoSure; the higher-order callers should do that. I love wrapping errors. But the issue is that the source of the problem is in lower-level code which made the theoretically correct decision to be specific what you return and generic in what you accept. That lower-level code cannot guarantee that its higher-order callers will wrap the errors it returns. So; the lower-level code has to remain generic. Obviously my example is contrived; but the problem is not. It is allowed, but essentially never safe to communicate to the compiler the error types your function returns. Its ok to use custom error types; you just can't tell the compiler about them. This is a problem that is so deep in the language it has influenced how error types and functions are designed in the standard library. I'm aware of one production outage related to this problem (yeah yeah, blame bad reviews and bad testing, I get it, it still happened). I've caught it a dozen times in code reviews (especially after that happened). Its a real problem. People don't write perfect code.
- randomdata 3y ago> but essentially never safe to communicate to the compiler the error types your function returns. It is safe as long as you don't write too much code for no reason. The problem in the example is that DatabaseError needlessly defined an Error method. It serves no purpose other than to allow the issue to arise. Remove said method, which has no reason to exist, and the program will fail to compile. > Its a real problem. People don't write perfect code. Okay, sure. It is possible that a programmer may, for whatever reason, leave out entire blocks of logic that the program needs. Imagine not adding billing logic to your storefront software – I'm sure it has happened to someone before! Making interfaces more intuitive does not solve that problem, though.
- burnished 3y agoWhy are you complaining that this person wrote a clear and small example that demonstrates what they are talking about? It is painfully rude; a description and a couple lines of code that runs is the gold standard for starting a programming conversation.
- kubanczyk 3y ago> It is safe as long as you don't write too much code for no reason. var errConcrete *strconv.NumError = nil var err error = errConcrete err != nil // true Please fix this one.
- kubanczyk 3y ago> the theoretically correct decision to be specific what you return and generic in what you accept Yet another case of that old pitfall. Untyped nil uses the same keyword as typed nil, when they are not equal. It's an unfortunate contradiction within the language. And it means that the entrenched practice of err != /* untyped */ nil is an overly specific check (nil error is harmless whether it contains the type information or not). Someone, somewhere has thrown at us a principle ("the theoretically correct decision") and someone else has thrown at us a conflicting principle ("err != nil is a universal idiom"). The problem: these are not principles! Both of these are decisions with tradeoffs. Who is making these decisions? Some blog authors or conference speakers? No: the team that owns the code. So, either: - have a linter that catches *ErrX return declarations automatically, - or, have a linter that prevents err != nil (generally: interface != nil) Since the latter is impractical today, one needs to decide the former. Solved, isn't it?
- kubanczyk 3y agoThe latter possibility connects closely to TFA, because if we have `^` notation, it can magically check for a typed nil.
- prerok 3y agoHardly a contrived problem, since I know of at least one occurrence where I had issues in production because of it. It was not error handling that was the problem, which is exposed here. The problem was that I had a variable of an interface type and in the code was producing different objects implementing that interface. Now, a nil value was also allowed if none of the cases matched and then the variable was stored in a sync.Map as a value. The problem was that when retriving the value the nil check never matched. Why? Well, it's type is defined as the interface even if it doesn't point to any implementation. I get that the designers might not have considered a different option: it is allowed to invoke a method on a nil object, making what some languages would call static methods. Still, I agree with the grandparent: I think this is Go's biggest flaw, simply because it is so surprising.