ree*_*ard 20 c++ coding-style language-design exception go
在我看来,Google的例外替代方案是
C++:断言(表达式)
GO:延迟/恐慌/恢复是在询问此问题后添加的语言功能
多值回报是否足以作为替代方案?为什么"断言"被视为替代品?如果发生错误而未正确处理错误,Google是否认为该程序可以正常运行?
Go不同寻常的功能之一是函数和方法可以返回多个值.这可以用来改进C程序中的几个笨拙的习语:带内错误返回(例如-1表示EOF)和修改参数.
在C中,写入错误由负计数发信号,错误代码在易失性位置分泌.在Go中,Write可以返回一个计数和一个错误:"是的,你写了一些字节但不是全部,因为你填充了设备".包os中*File.Write的签名是:
func (file *File) Write(b []byte) (n int, err Error)并且正如文档所述,当n!= len(b)时,它返回写入的字节数和非零错误.这是一种常见的风格; 有关更多示例,请参阅有关错误处理的部分.
Go函数的返回或结果"参数"可以给出名称并用作常规变量,就像传入参数一样.命名时,它们在函数开始时被初始化为其类型的零值; 如果函数执行不带参数的return语句,则结果参数的当前值将用作返回值.
名称不是强制性的,但它们可以使代码更短更清晰:它们是文档.如果我们命名nextInt的结果,很明显哪个返回int是哪个.
func nextInt(b []byte, pos int) (value, nextPos int) {由于命名结果已初始化并与简单的返回相关联,因此它们可以简化并澄清.这是一个使用它们的io.ReadFull版本:
func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
for len(buf) > 0 && err == nil {
var nr int;
nr, err = r.Read(buf);
n += nr;
buf = buf[nr:len(buf)];
}
return;
}
Run Code Online (Sandbox Code Playgroud)
例外是一个类似的故事.已经提出了许多异常设计,但每种设计都增加了语言和运行时的复杂性.就其本质而言,例外跨越功能,甚至可能是goroutines; 它们具有广泛的影响.人们还担心它们会对图书馆产生什么影响.根据定义,它们与支持它们的其他语言相比具有特殊的经验,表明它们对库和接口规范有深远的影响.很高兴找到一种设计,使它们真正卓越,而不会鼓励常见错误转变为需要每个程序员进行补偿的特殊控制流程.
与泛型一样,例外仍然是一个悬而未决的问题.
决定:
从表面上看,使用例外的好处超过了成本,特别是在新项目中.但是,对于现有代码,异常的引入会影响所有依赖代码.如果异常可以传播到新项目之外,那么将新项目集成到现有的无异常代码中也会出现问题.由于Google的大多数现有C++代码都没有准备好处理异常,因此采用生成异常的新代码相对比较困难.
鉴于Google的现有代码不能容忍异常,因此使用异常的成本比新项目中的成本要高一些.转换过程缓慢且容易出错.我们不认为可用的异常替代方案(例如错误代码和断言)会带来很大的负担.
我们反对使用例外的建议不是基于哲学或道德理由,而是基于实际理由.因为我们想在Google上使用我们的开源项目,如果这些项目使用例外情况很难这样做,我们也需要针对Google开源项目中的例外情况提出建议.如果我们不得不从头再做一遍,事情可能会有所不同.
延迟语句允许我们考虑在打开它之后立即关闭每个文件,保证无论函数中的返回语句数量如何,文件都将被关闭.
延迟语句的行为是直截了当且可预测的.有三个简单的规则:
1.在计算defer语句时,将计算延迟函数的参数.
在此示例中,在延迟Println调用时计算表达式"i".函数返回后,延迟调用将打印"0".
Run Code Online (Sandbox Code Playgroud)func a() { i := 0 defer fmt.Println(i) i++ return }2.在周围函数返回后,以后进先出顺序执行延迟函数调用.此功能打印"3210":
Run Code Online (Sandbox Code Playgroud)func b() { for i := 0; i < 4; i++ { defer fmt.Print(i) } }3.延迟函数可以读取并分配给返回函数的命名返回值.
在此示例中,延迟函数在周围函数返回后递增返回值i.因此,此函数返回2:
Run Code Online (Sandbox Code Playgroud)func c() (i int) { defer func() { i++ }() return 1 }这样便于修改函数的错误返回值; 我们很快就会看到一个例子.
恐慌是一种内置功能,可以阻止普通的控制流并开始恐慌.当函数F调用panic时,F的执行停止,F中的任何延迟函数都正常执行,然后F返回其调用者.对于呼叫者,F然后表现得像是对恐慌的呼唤.进程继续向上移动,直到当前goroutine中的所有函数都返回,此时程序崩溃.可以通过直接调用恐慌来启动恐慌.它们也可能由运行时错误引起,例如越界数组访问.
Recover是一个内置函数,可以重新控制恐慌的goroutine.恢复仅在延迟函数内有用.在正常执行期间,对recover的调用将返回nil并且没有其他效果.如果当前goroutine处于恐慌状态,则对恢复的调用将捕获给予恐慌的值并恢复正常执行.
这是一个示例程序,演示了恐慌和延迟的机制:
Run Code Online (Sandbox Code Playgroud)<snip>有关恐慌和恢复的真实示例,请参阅Go标准库中的json包.它使用一组递归函数对JSON编码的数据进行解码.当遇到格式错误的JSON时,解析器调用panic是将堆栈展开到顶级函数调用,该函数调用从恐慌中恢复并返回适当的错误值(请参阅decode.go中的'error'和'unmarshal'函数) .在regexp包的Compile例程中有一个类似的例子.Go库中的约定是即使包在内部使用panic,其外部API仍然会显示明确的错误返回值.
延迟的其他用法(超出前面给出的file.Close()示例)包括释放互斥锁:
Run Code Online (Sandbox Code Playgroud)mu.Lock() defer mu.Unlock
Ste*_*sop 14
多次返回并非Go独有,它们不能替代异常.在C(或C++)术语中,它们是返回包含多个值的struct(对象)的简洁且用户友好的替代.
如果这就是你的意思,它们确实提供了指示错误的便捷方法.
为什么"断言"被视为替代品?
断言最初用于调试.他们在处于"不可能"状态的情况下停止程序,设计说不应该发生,但无论如何.返回错误不太可能有用.代码库显然还没有工作,那么它究竟能成功恢复呢?当有一个需要注意的错误时,为什么你甚至想要呢?
在生产代码中使用断言有点不同 - 显然存在性能和代码大小问题,因此通常的方法是在代码分析和测试确信"不可能"的情况确实不可能时将其删除.但是,如果你在这种偏执程度上运行代码,它正在审计自己,那么你可能也是偏执狂,如果你让它继续在"不可能"状态下运行,那么它可能会做一些危险的破坏:腐败有价值的数据,超出堆栈分配并可能产生安全漏洞.再说一遍,你只想尽快关闭.
你使用断言的东西确实与你使用异常的东西不同:当编程语言如C++和Java为"不可能"的情况(logic_error,ArrayOutOfBoundsException)提供例外时,他们无意中鼓励一些程序员认为他们的程序应该尝试从他们真正失控的情况中恢复过来.有时这是合适的,但Java建议不要捕获RuntimeExceptions是有充分理由的.偶尔捕获一个是个好主意,这就是它们存在的原因.几乎总是抓住它们并不是一个好主意,这意味着它们无论如何都等于停止程序(或者至少是线程).