免责声明:
-
我最近才开始深入研究 golang 编译器,更具体地说:go 汇编器及其映射。因为我绝不是专家,所以我不会尝试在这里解释所有细节(因为我的知识很可能仍然缺乏)。我将在底部提供几个链接,可能值得查看以了解更多详细信息。
-
你试图做的事情对我来说意义不大。如果在运行时,您试图修改一个函数,那么您之前可能做错了什么。这只是以防你想弄乱任何功能。您尝试使用 strings 包中的函数做某事的事实使这更加令人担忧。 reflect 包允许您编写非常通用的函数(例如,具有请求处理程序的服务,但您想将任意参数传递给这些处理程序需要您有一个处理程序,处理原始请求,然后调用相应的处理程序。您不可能知道那个处理程序是什么样的,所以你使用反射来计算出所需的参数......)。
现在,函数是如何实现的?
Go 编译器是一个复杂的代码库,但值得庆幸的是,语言设计及其实现已经被公开讨论过。据我所知,golang 函数的编译方式基本上与 C 语言中的函数几乎相同。但是,调用函数有点不同。 Go 函数是一流的对象,这就是为什么您可以将它们作为参数传递、声明函数类型以及为什么 reflect 包必须允许您对函数参数使用反射。
本质上,函数不是直接处理的。函数通过函数“指针” 传递和调用。函数实际上是类似于map 或slice 的类型。它们持有指向实际代码和调用数据的指针。简单来说,将函数视为一种类型(在伪代码中):
type SomeFunc struct {
actualFunc *func(...) // pointer to actual function body
data struct {
args []interface{} // arguments
rVal []interface{} // returns
// any other info
}
}
这意味着reflect 包可用于例如计算函数期望的参数数量和返回值。它还告诉您返回值是什么。整个函数“类型”将能够告诉您函数所在的位置,以及它期望和返回的参数,但仅此而已。 IMO,这就是你真正需要的。
由于这种实现,您可以创建具有如下函数类型的字段或变量:
var callback func(string) string
这将创建一个基础值,根据上面的伪代码,它看起来像这样:
callback := struct{
actualFunc: nil, // no actual code to point to, function is nil
data: struct{
args: []interface{}{string}, // where string is a value representing the actual string type
rVal: []interface{}{string},
},
}
然后,通过分配与args 和rVal 约束匹配的任何函数,您可以确定callback 变量指向的可执行代码:
callback = strings.ToUpper
callback = func(a string) string {
return fmt.Sprintf("a = %s", a)
}
callback = myOwnToUpper
我希望这能解决 1 或 2 件事,但如果没有,这里有一堆链接可能会更清楚地说明这个问题。
更新
当您尝试更换您正在使用的用于测试目的的函数时,我建议您不要使用反射,而是注入模拟函数,这是 WRT 更常见的做法测试开始。更不用说它变得容易多了:
type someT struct {
toUpper func(string) string
}
func New(toUpper func(string) string) *someT {
if toUpper == nil {
toUpper = strings.ToUpper
}
return &someT{
toUpper: toUpper,
}
}
func (s *someT) FuncToTest(t string) string {
return s.toUpper(t)
}
这是一个关于如何注入特定函数的基本示例。在您的foo_test.go 文件中,您只需调用New,传递一个不同的函数。
在更复杂的场景中,使用接口是最简单的方法。只需在测试文件中实现接口,并将替代实现传递给New函数:
type StringProcessor interface {
ToUpper(string) string
Join([]string, string) string
// all of the functions you need
}
func New(sp StringProcessor) return *someT {
return &someT{
processor: sp,
}
}
从那时起,只需创建该接口的模拟实现,您就可以测试所有内容,而无需费力地反射。这使您的测试更易于维护,并且由于反射很复杂,因此您的测试出错的可能性大大降低。
如果您的测试有问题,它可能会导致您的实际测试通过,即使您尝试测试的代码不起作用。如果测试代码比你开始覆盖的代码更复杂,我总是怀疑......