实践中的总结四:变量只赋值一次

2025/12/29 总结 实践 共 1077 字,约 4 分钟

变量只赋值一次

一句话总结:尽量保持引用的不可变性(Immutability)。当状态需要流转或变更时,与其原地修改,不如赋予它一个新的名字。

简介

叫做变量却要尽量不要改变,真的很神秘。

如果严格遵循“变量只赋值一次”的原则,那么数据的流向会更加清晰,它们的生命周期变得可预测,我们对于变量的作用,逻辑的全部流程也会更容易掌握。

同时,利于分解逻辑,重新命名了,不同数据结构对应的作用域自然会界限分明,逻辑也自然分段,易于下一步的抽象。

然而很多场景是必然要改变的,比如循环,这行情况之外我们应尽量避免变更。

正面例子:Rust 已经将这个设计理念完全融合到了语言设计哲学中,它的变量初始化时就是不可变的,只有显式加了 mut 关键字,变量才被允许修改,逼迫数据尽量维持不变性质。

反面例子:Java,final 关键字修饰对象时,只保证了“引用不可变”,而无法保证“对象内容不可变”,只有修饰 String 这种天生不可变的类或者原始类型的数据类似 int,才有意义,无法保证我们想要的不变性。

例子

举个 Go 中内置的函数:append 为例子。该函数的作用是将元素或其他切片追加到原切片后,并返回一个新的切片。

经常这样使用:

	s := []int{1, 2, 3}
	// 其他逻辑	
	s = append(s, 4, 5, 6)
	// 其他逻辑	

这样是没问题的。不过我认为这样更好:

	s := []int{1, 2, 3}
	// 其他逻辑
	s1 := append(s, 4, 5, 6)
	// 其他逻辑	

记得要取有实际意义的变量名。s 作为旧切片,任务已经完成,可以回收了。

另一个例子:一小段 if 逻辑来确定 s 最终的值,代码中经常需要处理这种情况。

s := 1 // 默认值
if a == 2 {
    s = 3 // 修改
} else if a == 4 {
    s = 5 // 修改
}

比起上面,我更倾向于下面的方式:

var s int // 声明但不初始化
if a == 2 {
    s = 3
} else if a == 4 {
    s = 5
} else {
    s = 1 // 显式赋值
}

用声明,然后只赋值一次的方式来搭建这个逻辑,能减少我们的心智负担。我们需要确认的是 s 在不同条件下被赋予了什么值,而不是去追踪它在某行代码之前是否已经被修改过。

总结

通过命名新变量来取代变更原变量,也就是用空间换取逻辑的清晰度。有人可能会担心性能问题:如果编译器不进行特定优化(如 SSA 优化),确实可能需要开辟新的内存空间。

然而在大部分其他场景,特别是需要面临更多潜在的变更的情况下,可维护性优于微小的性能损耗。越是复杂的逻辑,保持数据流向的简洁和清晰,不要引入过多的状态分支,能降低复杂度和编码者的心智负担,让复杂的逻辑不会进一步膨胀,易于修改与理解。

文档信息

Search

    Table of Contents