原文
How does Go get exceptions right? Why, by not having them in the first place.
Go 是怎么正确处理异常的?嗨,它一开始就没有这些。

First, a little history 首先,来了解下历史。

Before my time, there was C, and errors were your problem. This was generally okay, because if you owned an 70’s vintage mini computer, you probably had your share of problems. Because C was a single return language, things got a bit complicated when you wanted to know the result of a function that could sometimes go wrong. IO is a perfect example of this, or sockets, but there are also more pernicious cases like converting a string to its integer value. A few idioms grew to handle this problem. For example, if you had a function that would mess around with the contents of a struct, you could pass a pointer to it, and the return code would indicate if the fiddling was successful. There are other idioms, but I’m not a C programmer, and that isn’t the point of this article.
最早之前,是 C 语言,错误是你的需要处理的问题。通常是可以的,因为如果你拥有一台70年代的老式迷你电脑,你可能也会遇到同样的问题。因为 C 是一个单一返回的语言,当你想知道一个函数返回值时就会比较复杂,因为函数有可能会出错。IO 或者 sockets 就是一个很好的例子,不过还有更恶劣的情况,比如将字符串转换为整数值。逐渐出现一些语法来解决这些问题。例如,有一个处理结果提内容的函数,你可以传递一个结构体指针,然后使用返回码判断是否成功处理。还有其他的语法,但我不是程序员而且不是本文的重点。
Next came C++, which looked at the error situation and tried to improve it. If you had a function which would do some work, it could return a value or it could throw an exception, which you were then responsible for catching and handling. Bam! Now C++ programmers can signal errors without having to conflate their single return value. Even better, exceptions can be handled anywhere in the call stack. If you don’t know how to handle that exception it’ll bubble up to someone who does. All the nastyness with errno and threads is solved. Achievement unlocked!
然后是 C++,它考虑到错误情况并试图改进它。如果有一个可以做一些事情的函数,它可以返回相应的值或者抛出一个异常,然后你负责进行捕获和处理。砰!现在 C++ 程序员就可以在不混淆返回值的情况下表示错误信息。更好的是,可以在调用堆栈的任何地方处理这些异常。如果你不知道如何处理那个异常,它就会向上抛出给知道的人。所有关于错误代码和线程的麻烦都解决了。成就达成!
Sorta.(不是很清楚怎么翻译,某种程度上?)
The downside of C++ exceptions is you can’t tell (without the source and the impetus to check) if any function you call may throw an exception. In addition to worrying about resource leaks and destructors, you have to worry about RAII and transactional semantics to ensure your methods are exception safe in case they are somewhere on the call stack when an exception is thrown. In solving one problem, C++ created another.
C++ 异常的缺点是你无法判断(没有源代码和主动检查)你调用的函数是否抛出异常。除了担心资源泄漏和析构函数之外,还必须担心 RAII 和事务语义,以确保您的方法是异常安全的,以防它们在抛出异常时位于调用堆栈中的某个位置。在解决一个问题时,C++ 创造了另一个问题。
So the designers of Java sat down, stroked their beards and decided that the problem was not exceptions themselves, but the fact that they could be thrown without notice; hence Java has checked exceptions. You can’t throw an exception inside a method without annotating that method’s signature to indicate you may do so, and you can’t call a method that may throw an exception without wrapping it in code to handle the potential exception. Via the magic of compile time bondage and discipline the error problem is solved, right?
于是,Java 的设计者们坐下来,捋了捋他们的胡子,认定问题不在于异常本身,而在于它们可以在没有任何通知的情况下被抛出去;因此 Java 有异常检查。除非你在方法的注释中表明该方法可以抛出异常,否则你不能在方法内部抛出异常,而且不能在没有异常处理代码的包装下去调用一个可能抛出异常的方法。通过编译时的约束和规则的魔力,是不是错误问题就解决了?
This is about the time I enter the story, the early millennium, circa Java 1.4. I agreed then, as I do now, that the Java way of checked exceptions was more civilised, safer, than the C++ way. I don’t think I was the only one. Because exceptions were now safe, developers started to explore their limits. There were coroutine systems built using exceptions, and at least one XML parsing library I know of used exceptions as a control flow technique. It’s commonplace for established Java webapps to disgorge screenfuls of exceptions, dutifully logged with their call stack, on startup. Java exceptions ceased to be exceptional at all, they became commonplace. They are used from everything from the benign to the catastrophic, differentiating between the severity of exceptions falls to the caller of the function.
千禧年早期,我进入这个故事的时间,大约是 Java 1.4。我一直认为 Java 异常的检查方式比 C++ 的方式更文明、更安全。我想不止我一个人这样。由于异常现在是安全的,开发人员开始探索它的限制。有使用异常构建的协程系统,我知道至少有一个 XML 解析库使用异常作为控制流技术。对于已建立的 Java Web 应用程序来说,在启动时抛出大量异常并尽职尽责地记录它们的调用堆栈是司空见惯的。Java 异常不再是例外,而是变得司空见惯。它们可用于从良性到灾难性的各种情况,区分异常的严重程度取决于函数的调用者。
If that wasn’t bad enough, not all Java exceptions are checked, subclasses of java.Error and java.RuntimeException are unchecked. You don’t need to declare them, just throw them. This probably started out as a good idea, null references and array subscript errors are now simple to implement in the runtime, but at the same time because every exception Java extends java.Exception any piece of code can catch it, even if it makes little sense to do so, leading to patterns like catch (e Exception) { // ignore }
如果这还不够糟糕,那么并不是所有的 Java 异常都被检查,java.Error 和 java.RuntimeException 的子类没有被检查。你不需要声明它们,只需要抛出它们。这可能一开始是一个好主意,空引用和数组下标错误现在很容易在运行时实现,但同时因为每个异常 Java 扩展 java.Exception 任何一段代码都可以捕获它,即使它很少这样做的意义,导致像这样的模式 catch (e Exception) { // ignore }
So, Java mostly solved the C++ unchecked exception problem, and introduced a whole slew of its own. However I argue Java didn’t solve the actual problem, the problem that C++ didn’t solve either. The problem of how to signal to caller of your function that something went wrong.
所以,Java 主要解决了 C++ 没有异常检查的问题,并引入了自己的一整套方法。然而,我认为 Java 并没有解决实际问题,C++ 也没有解决实际问题。如何向函数的调用者发出出错信号的问题。

Enter Go 说说 Go

Go solves the exception problem by not having exceptions. Instead Go allows functions to return an error type in addition to a result via its support for multiple return values. By declaring a return value of the interface type error you indicate to the caller that this method could go wrong. If a function returns a value and an error, then you can’t assume anything about the value until you’ve inspected the error. The only place that may be acceptable to ignore the value of error is when you don’t care about the other values returned.
Go 通过没有异常处理的方式来解决异常问题。相反,Go 通过支持多个返回值,允许函数除了返回结果之外还返回一个错误类型。通过声明接口类型error的返回值,可以向调用者指出此方法可能出错。如果一个函数返回一个值和一个错误,那么在检查错误之前,你不能对这个值做任何假设。唯一可以忽略 error 值的地方是当你不关心返回的其他值时。
Go does have a facility called panic, and if you squint hard enough, you might imagine that panic is the same as throw, but you’d be wrong. When you throw and exception you’re making it the caller’s problem throw new SomeoneElsesProblem();
Go 确实有一个叫做 panic 的工具,如果你眯着眼睛看,你可能会认为 panic 和 throw 是一样的,但你错了。当你抛出异常时,你就把它变成了调用者的问题throw new SomeoneElsesProblem();
For example in C++ you might throw an exception when you can’t convert from an enum to its string equivalent, or in Java when parsing a date from a string. In an internet connected world, where every input from a network must be considered hostile, is the failure to parse a string into a date really exceptional? Of course not.
例如,在 C++ 中,当你无法从枚举转换为其等效字符串时,或者在 Java 中从字符串解析日期时,你可能会抛出异常。在互联网的世界里,每个来自网络的输入都必须被认为是恶意的,不能将字符串解析为日期真的是例外吗?当然不是。
When you panic in Go, you’re freaking out, it’s not someone elses problem, it’s game over man.
当你在 Go 中使用 panic 时,你会被吓坏,这不是别人的问题,这是游戏结束。
panic("inconceivable")
panics are always fatal to your program. In panicing you never assume that your caller can solve the problem. Hence panic is only used in exceptional circumstances, ones where it is not possible for your code, or anyone integrating your code to continue.
painc 对你的程序是致命的。panic 时你永远不要认为调用者可以解决这个问题。
The decision to not include exceptions in Go is an example of its simplicity and orthogonality. Using multiple return values and a simple convention, Go solves the problem of letting programmers know when things have gone wrong and reserves panic for the truly exceptional.
在 Go 中不包含异常的决定是其简单性和正交性的一个例子。使用多个返回值和一个简单的约定,Go 解决了让程序员知道什么时候出现问题并为真正异常的情况保留了 panic 。