Golang不包含也不打算包含试捕-最终机制,但是有一个堆栈解扰与恢复机制,人们使用它来模拟异常处理。
作为一个纯正的Golang学习练习,我重新发明了试捕--最终轮转(然后用示例中更优雅的代码的一部分来更新它),据我所知,它确实正确地工作。这不是一个规范的golang模式,这与这个活动无关。
这个代码评审请求是关于如何彻底地测试这样一个构造(当然,试图捕获-最终的任何逻辑缺陷都是受欢迎的),并以良好的结构方式进行测试。这个测试通过并提供了完整的覆盖范围,但我不确定我是否测试过所有与语言可能性/行为相关的非琐碎情况或特殊行为,我希望您能提供任何风格/测试结构改进建议。
要进行单元测试的代码:
package goUtil
// PanicException performs a traditional try, catch, finally block.
// You can't rethrow a panic so you have to consider when you really want to catch.
// If you catch a panic then unless you harvest the stack trace then you have lost it.
func PanicException(try func(), catch func(interface{}), finally func()) {
if finally != nil {
// Ensure any finally is executed
defer finally()
}
if catch != nil {
defer func() {
if r := recover(); r != nil {
// Execute the catch passing in the panic object
catch(r)
}
}()
}
// try and invoke the function call
try()
}我的单元测试代码供评审:
package goUtil
import (
"fmt"
"testing"
)
func TestTryCatchFinallyOperation(t *testing.T) {
i := 1
obj := &i
innerTryCalled := false
innerTryCompletes := false
innerCatchCalled := false
innerCatchCompleted := false
middleTryCompletes := false
middleCatchCalled := false
middleFinallyCalled := false
outerTryCompletes := false
outerFinallyCalled := false
// The inner try panics, the inner catch re-panics but the middle catch swallows the panic
PanicException(func() {
// Outer try
PanicException(func() {
// Middle try
PanicException(func() {
// Inner try; throw panic
innerTryCalled = true
panic(obj)
innerTryCompletes = true
}, func(e3 interface{}) {
// Inner catch; re-panic
innerCatchCalled = true
FailIfFalse(t, e3 == obj, "The panic object has wrongly changed")
panic(e3)
innerCatchCompleted = true
}, nil)
middleTryCompletes = true
}, func(e2 interface{}) {
// Middle catch; swallow panic
middleCatchCalled = true
FailIfFalse(t, e2 == obj, "The panic object has wrongly changed")
}, func() {
// Middle finally
middleFinallyCalled = true
})
outerTryCompletes = true
}, nil,
func() {
// Outer fina
outerFinallyCalled = true
})
// Check the execution path
FailIfFalse(t, innerTryCalled == true, "The inner try was wrongly never called")
FailIfFalse(t, innerTryCompletes == false, "The inner try should not have completed execution => panic did not unwind stack")
FailIfFalse(t, innerCatchCalled == true, "The inner catch failed to catch the panic")
FailIfFalse(t, innerCatchCompleted == false, "The inner catch failed to re-panic")
FailIfFalse(t, middleTryCompletes == false, "The middle try should not have completed => the inner catch failed to re-panic")
FailIfFalse(t, middleCatchCalled == true, "The middle catch failed to cath the panic")
FailIfFalse(t, middleFinallyCalled == true, "The middle finally was not called when there was no panic")
FailIfFalse(t, outerTryCompletes == true, "The outer try did not complete execution => the middle catch falied to catch the panic")
FailIfFalse(t, outerFinallyCalled == true, "The outer finally failed to run when there was no panic")
// No panic should escape the TryCatchFinally stack above which would trigger the test to fail automatically
}
type TestObj interface {
Log(args ...interface{})
Fail()
}
// FailIfFalse will invoke t.Fail() if pass is false and generate a log message if failFormat is not ""/nil.
// The failArgs are optional and if present will be args into a t.Logf(failFormat, failArgs...) equivalent call.
// The value of pass is returned.
func FailIfFalse(t TestObj, pass bool, failFormat string, failArgs ...interface{}) bool {
if !pass {
if len(failFormat) != 0 {
var txt string
if str, _, _, _, ok := GetFileAndLineNo(1); ok {
txt = "\n" + str + "> "
}
if len(failArgs) == 0 {
txt += failFormat
} else {
txt += fmt.Sprintf(failFormat, failArgs...)
}
t.Log(txt)
}
t.Fail()
}
return pass
}支持文件和行功能:
package goUtil
import (
"fmt"
"path/filepath"
"runtime"
)
// GetFileAndLineNo returns information about the file and line in skip levels up the call tree (0 = location this function is called)
func GetFileAndLineNo(skip int) (text string, pc uintptr, file string, line int, ok bool) {
pc, file, line, ok = runtime.Caller(skip + 1)
if ok {
text = fmt.Sprintf("%s:%d", filepath.Base(file), line)
}
return text, pc, file, line, ok
}发布于 2018-03-15 10:09:49
正如伊莱亚斯强烈敦促你的,这不是个好主意。定制的做事方式,破译代码库,使库使用困难和令人沮丧。对代码结构可能产生的后果可能是鼓励过度使用类似lamda的功能(虽然像style这样的好对象可以在某种程度上减轻这种情况)和由于表达式的复杂性而变得不可读。
尽管如此,为了回答您的问题,我看不出您在单元测试覆盖率中遗漏了什么。gotest给了您一个很好的代码覆盖率,但这本身并不意味着您已经测试了所有的语言特性。
不管函数是如何退出的,函数结束时都会调用Golang守护人,不管是否恐慌。恢复将返回控制流给您,除非有一个重大问题(例如内存不足);现在还不清楚会发生什么。
具体而言,你已经成功地涵盖了:
请注意,任何真正恐慌的代码都会被恐慌处理程序破坏其堆栈跟踪显示。任何显示堆栈跟踪的错误报告都会降低其价值。
发布于 2017-06-17 23:19:55
虽然您的方法确实很有趣,但我不得不说,创建一个尝试捕获-最终机制的基本想法是我建议不要的。
正如您所说的,go没有尝试捕获ATM,而且这些结构不太可能在短期内被添加(如果有的话)。语言设计者对错误处理采取了不同的方法。如果还没有,我建议您查看go中错误处理的这篇黄金博客文章。另外,请确保查看延迟发布、恐慌和恢复流量的帖子,并查看其他可能感兴趣的帖子。在go博客中确实有一些有用的信息。
尽管如此,我认为你是想让戈朗以一种对你来说很自然的方式去做一些事情,因为你在其他语言中使用了尝试捕获--最终的构造。问题是: Go就是Go,而不是其他语言中的一种。为了充分利用任何工具,学习如何使用它,不要让它做其他语言所做的事情。如果你诚实的话,你一定知道这是个小问题,而且黑客是不好的,嗯。
首先,catch回调依赖于try代码来引起恐慌。这意味着您可能会在代码中过度使用panic。您将在go中看到大量的资源,告诉您尽量避免使用panic,您的方法不仅鼓励它:它依赖于panic,这意味着您没有完全遵循最佳实践。
你在这里问如何测试这段代码。然而,这方面有两个问题:
我想我想说的是:我喜欢这个想法,而且你成功地模仿了围棋的尝试-捕捉-最后的流程,这是语言表达能力的一个很好的例子。但是在现实生活中使用这一点意味着您的代码更难维护、测试和调试。这将使诊断问题更加困难,并且您编写的应用程序可能不那么健壮。因为这个原因,我不会用你的方法。
https://codereview.stackexchange.com/questions/165911
复制相似问题