翻译转载来源:https://massimo-nazaria.github.io/blog/2022/02/10/avoid-spaghetti-code-with-scope-minimization.html
让我们看看一些关于如何通过最小变量的可见性来防止混乱代码现象的建议吧。我们的目标是尽可能减少代码中变量在源码的可见部分,即减少变量的范围。
范围最小化是以一种易于实现的方式构造代码的过程,通常以以下方式实现:
1. 声明具有最小范围的变量
2. 给变量赋值具有最小范围的数据。
事实上,是代码结构定义了变量的可见性。
背景概念
一段程序是由简单(如:赋值)或集合(如:条件或循环)语句组合而成的。集合语句是可以被嵌套的,这也意味着集合语句可以由包含其他语句的代码块构成。
当有如下A或B代码块需要特殊考虑:
1. 当A包含B时,我们称A是B的外部块,同时
2. 我们称B是A的内部块。
块的缩进级别是其嵌套级别的数量,相当于一个块比起其外部块高一个缩进级别。
让我们将全局范围定义为没有外部块且缩进为0的特殊块。
最后,全局变量是在全局范围内定义的变量。
变量的可见性规则
一个变量在以下代码部分是可见的主要有:
1. 从变量的声明语句开始,
2. 结束于变量声明块结束时,以及,
3. 包含在1和2之间的所有嵌套块。
相辅相成的是,变量在以下代码部分中不可见:
1. 在变量声明之前,以及
2. 变量声明块结束后。
建议
1. 永远不要使用全局变量
2. 声明单用途变量
3. 声明接近其使用的变量
4. 保持代码块越小越好
5. 使用靠近其声明的变量
6. 使用不超过两层的嵌套级别
R1. 永远不要使用全局变量
永远不要定义或使用全局变量,它会使代码变得更难阅读、维护和测试。详情见“全局变量是坏的”。
全局变量的使用还增加了问题的副作用的发生,这常常导致不容易识别和修复的编程错误。
程序中错误赋值(全局)变量的语句越少越好。
总之,使用全局变量通常代表技术债务,必须通过代码重构尽快偿还!
R2. 声明单用途变量
为单个特定目的声明和使用变量,以便使其范围限制到最小。
声明变量的用途越多,变量可见的语句数量就越多。
变量可见的语句数量越多,赋值变量错误的语句可能会越多。
赋值变量错误的语句越多,就越难查找和修复潜在的错误。
总结:只定义和使用单用途变量
R3. 声明接近其使用的变量
声明尽可能靠近使用语句和代码块的变量。
与建议R2严格相关,这是减少可以使用声明变量的语句数量的另一种方式。
举例:三个后续块
首先,让我们思考三个后续代码块A、B和C,即:
ABC位于统一外部块中,并且
他们具有相同的缩进级别。
然后,让我们在A之前声明一个变量v,它的缩进级别与A相同。
通过上述的可见性规则,变量v对于三个后续块A、B、C都是可见的。
现在让我们假设我们需要变量v只对C可见。
很明显,在C之前声明v会降低它的可见性,因为它只会在C中的语句中可见。到目前为止一切都很好!
然而,如果在不遵循下一项建议的情况下遵循该建议,则该建议仅部分有用。
R4. 保持代码块越小越好
类似于单一变量用途的建议R2,通过使代码块集中于单个特定任务,来使代码块尽可能小。
否则,专用于不同任务的代码块部分可能会不必要地看到某些变量。
让我们再看看前面介绍过的三个后续块A、B和C,它们具有相同的缩进层。
假设我们需要声明一个变量w,由A使用和修改。而在B、C中只能读取。
在A之前声明w是不可避免的,因此其范围将不必要地包括B和C。
这使得在希望它们只能读取w的值时,它们也能够赋值w。
我们如何避免这种情况?只需将B和C分成两个不同的函数,它们将w作为一个参数。
总结:使代码块成为单个任务,并将子任务移动到单独的函数中。
5. 使用靠近其声明的变量
请记住使用尽可能接近其声明的变量。
让我们假设你需要使用(无论是读还是赋值)一个声明在外部块的变量v。
请记住尽量减少使用 v 的语句和 v 的声明块之间的缩进级别。
一个通用的规则:2层应该是变量使用语句和变量声明之间最大的嵌套层数。
为了完整起见,让我们来看一个例子,在该示例中,在三个嵌套级别(即比推荐的多一个)的距离处使用变量可能是合理的。
例如:矩阵乘法算法
让我们考虑使用三个嵌套循环的迭代矩阵乘法算法。
此类循环中最深的一个包含此赋值语句:
语句使用的 A 和 B 声明在距离的三个嵌套级别的外部块中,这比建议的最大值高一个级别。
像这种情况,对这条规则破例是没有问题的。
除了极端情况外,在使用外部块中声明的变量时,请将三层缩进视为太多的缩进层级。
尽管建议 R6 实际上可以满足需求,但在组织代码时记住这个建议总是一个好主意。
R6. 使用不超过两层的嵌套级别
在一般情况下,请记住尽可能的减少嵌套块的深度。
每个函数中的最大嵌套深度最多应为两个。
当函数的嵌套深度大于2时,请按以下方式对其进行重构:
1.将一些子块移动到单独的函数中,以及
2.将移动的子块使用的变量作为函数的参数传递。
但是如建议R5所示,有时将三层嵌套深度作为最大深度也是合理的。
同样,除了极端情况外,始终将两层视为最大嵌套深度。