最近需要修复json,查看以前的信息,用的是github.com/RealAlexand... 。
这个包能力很强,大部分json都能修复。但包有个很严重的问题,某种情况下可能触发 stack overflow。
go
func main() {
str := `
{
"Be": "",
"gone": ""
}
",п"г`
dst, err := oldrepair.RepairJSON(str)
fmt.Println(dst, err)
}
arduino
➜ my go run main.go
runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0x140200e0390 stack=[0x140200e0000, 0x140400e0000]
fatal error: stack overflow
runtime stack:
runtime.throw({0x104d79071?, 0x100000000?})
exit status 2
后续换了新包github.com/kaptinlin/j... overflow,但是修复能力差不多弱了三倍,另外有可能panic,好在panic可以recover。
所以这引出了三个问题:
- 选择开源代码需要注意什么?
- stack overflow是怎么发生的?
- stack overflow发生后有什么补救措施。
选择开源代码的注意事项
我很少引用外部的开源代码,因为结果比较难以把控。印象比较深的一次是为了整数区间计算,这个比较小众,整数区间计算,我这么设计,好在是google家的,我在使用前做了大量的单元测试,上线后效果很好。
在一些相对小众的功能上,在选择 GitHub 上的开源代码时,我觉得可以从以下多个关键方面进行考虑:
项目活跃度
- 提交频率:查看项目的提交历史,如果在近期有频繁的代码提交,说明项目处于活跃开发状态,开发者在持续对其进行改进、修复问题。比如一个热门的前端框架,每周都有几次提交,这意味着它在不断发展。
- 问题处理情况:关注项目中 Issues(问题)板块,看新问题的创建频率以及已有问题的解决速度。如果问题能够在短时间内得到回应和处理,表明项目有活跃的维护团队。例如一些知名的开源数据库项目,能在几天内就对用户提出的问题进行解答和修复。
- 分支合并情况:频繁的分支合并说明项目在不断整合新功能和改进。比如一个大型的开源电商系统,经常有功能分支合并到主分支,代表项目在持续迭代。
社区支持
- 星星数量:一颗星表示用户对项目的喜爱和关注,星星数量越多,说明项目越受欢迎和受认可。像一些知名的机器学习框架,星星数量能达到十几万甚至更高。
- Fork 数量:Fork 数量多意味着有很多人基于这个项目进行二次开发,侧面反映了项目的可扩展性和受欢迎程度。例如一些经典的开源博客框架,有大量的 Fork。
- 参与人数:查看贡献者列表,参与人数多说明项目有一个活跃的社区,不同背景的开发者共同维护和改进项目。例如一些知名的开源操作系统,有来自全球各地的上千名开发者参与。
代码质量
- 代码结构:良好的代码结构应该是清晰、模块化的,易于理解和维护。可以查看项目的目录结构、文件组织方式以及代码中的注释情况。比如一个遵循 MVC(模型 - 视图 - 控制器)架构的 Web 项目,代码结构层次分明。
- 测试覆盖率:高测试覆盖率表明代码经过了充分的测试,质量更有保障。可以查看项目中的测试文件和相关测试报告。例如一些严谨的开源金融项目,测试覆盖率能达到 90% 以上。
- 遵循的规范:查看项目是否遵循行业内的最佳实践和代码规范,这有助于保证代码的一致性和可读性。比如一个 Java 项目遵循阿里巴巴的 Java 开发手册规范。
许可证
- 开源协议类型:不同的开源协议对使用、修改和分发代码有不同的规定。例如,MIT 协议非常宽松,允许自由使用、修改和分发代码;而 GPL 协议则要求基于该协议开源的代码所衍生的项目也必须开源。如果是商业项目使用开源代码,需要特别注意许可证是否允许商业使用。比如一个商业软件想使用某个开源库,就需要确保该开源库的许可证允许商业用途。
其它
- 大厂背书:看看是个人开发者还是大厂推出的,一般而言大厂的质量会更好一点
- 充足测试:在使用前,对自己要用的功能,做充足的单元测试,防止意外发生
stack overflow出现原因
Go 语言运行时会为每个 goroutine 分配一定大小的栈空间(初始栈大小一般较小,随着需求会动态增长),但当上述情况使得栈空间的使用超出了限制时,就会出现stack overflow
错误 。我们可以通过debug.SetMaxStack()控制栈的大小。
在 Go 语言中,stack overflow
(栈溢出)通常由以下几种情况产生:
无限递归调用
递归函数在没有正确的终止条件时,会不断地调用自身,导致栈上的函数调用信息不断增加,最终耗尽栈空间。例如:
go
package main
func recursiveFunction() {
recursiveFunction()
}
func main() {
recursiveFunction()
}
在上述代码中,recursiveFunction
函数没有终止条件,会一直递归调用自己,很快就会引发栈溢出错误。
深层嵌套调用
即使不是无限递归,如果函数调用的层次过深,也可能导致栈溢出。例如,一系列函数层层调用,每层调用都在栈上增加新的帧:
go
package main
func func1() {
func2()
}
func func2() {
func3()
}
// 假设这里有很多类似层层调用的函数
func funcN() {
// 没有实际操作,仅作为深层调用示例
}
func main() {
func1()
}
如果这种嵌套调用的层次足够深,超过了 Go 运行时分配给栈的空间大小,就会发生栈溢出。
栈上数据过大
如果在函数调用过程中,函数的局部变量占用了大量的栈空间,并且同时有较多的函数调用在栈上,也可能导致栈溢出。例如:
go
func largeStackFunction() {
// 创建一个非常大的数组,占用大量栈空间
bigArray := make([]int, 10000000000)
// 这里可以有更多的操作
fmt.Println(bigArray[0], bigArray[500])
}
func main() {
debug.SetMaxStack(1)
for i := 0; i < 10; i++ {
go largeStackFunction()
}
time.Sleep(50 * time.Second)
}
在这个例子中,largeStackFunction
函数内创建了一个非常大的数组,每次调用该函数都会在栈上占用较多空间,多次调用后可能会引发栈溢出。
stack overflow解决方案
在 Go 语言中,stack overflow
(堆栈溢出)无法通过常规的recover
机制捕获。这是因为堆栈溢出是一种底层的、严重的运行时错误,它会破坏调用栈的完整性,导致recover
无法正常工作。
虽然无法捕获堆栈溢出错误,但可以采取一些措施来预防它:
- 设置递归终止条件:在递归函数中,确保有明确的终止条件,避免无限递归。
- 优化递归算法:对于深度递归的算法,可以考虑使用迭代或尾递归优化来减少栈空间的使用。
- 合理使用局部变量 :避免在函数内部声明过大的局部变量,尽量使用堆内存(通过
new
或make
)来分配较大的数据结构。
资料
最后
大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)
我的个人博客为:shidawuhen.github.io/
往期文章回顾: