最近需要修复json,查看以前的信息,用的是https://github.com/RealAlexandreAI/json-repair 。
这个包能力很强,大部分json都能修复。但包有个很严重的问题,某种情况下可能触发 stack overflow。
1 | func main() { |
1 | ➜ my go run main.go |
后续换了新包https://github.com/kaptinlin/jsonrepair,虽然不会stack 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
(栈溢出)通常由以下几种情况产生:
无限递归调用
递归函数在没有正确的终止条件时,会不断地调用自身,导致栈上的函数调用信息不断增加,最终耗尽栈空间。例如:
1 | package main |
在上述代码中,recursiveFunction
函数没有终止条件,会一直递归调用自己,很快就会引发栈溢出错误。
深层嵌套调用
即使不是无限递归,如果函数调用的层次过深,也可能导致栈溢出。例如,一系列函数层层调用,每层调用都在栈上增加新的帧:
1 | package main |
如果这种嵌套调用的层次足够深,超过了 Go 运行时分配给栈的空间大小,就会发生栈溢出。
栈上数据过大
如果在函数调用过程中,函数的局部变量占用了大量的栈空间,并且同时有较多的函数调用在栈上,也可能导致栈溢出。例如:
1 | func largeStackFunction() { |
在这个例子中,largeStackFunction
函数内创建了一个非常大的数组,每次调用该函数都会在栈上占用较多空间,多次调用后可能会引发栈溢出。
stack overflow解决方案
在 Go 语言中,stack overflow
(堆栈溢出)无法通过常规的recover
机制捕获。这是因为堆栈溢出是一种底层的、严重的运行时错误,它会破坏调用栈的完整性,导致recover
无法正常工作。
虽然无法捕获堆栈溢出错误,但可以采取一些措施来预防它:
- 设置递归终止条件:在递归函数中,确保有明确的终止条件,避免无限递归。
- 优化递归算法:对于深度递归的算法,可以考虑使用迭代或尾递归优化来减少栈空间的使用。
- 合理使用局部变量:避免在函数内部声明过大的局部变量,尽量使用堆内存(通过
new
或make
)来分配较大的数据结构。