【发布时间】:2017-10-14 04:10:06
【问题描述】:
总结:
我在我的 Swift 代码中犯了一个错误,我已经修复了它。然后我问自己为什么会发生这种情况以及如何避免它。我尝试了一些方法,但没有任何帮助。
我把错误和我的想法放在下面。我希望你能教我正确的方法来避免这种错误,但任何想法、提示或建议都将不胜感激。
如何避免这种逻辑错误?
以下是我在斯坦福 cs193p 课程中的作业节选。
class SomeClass {
...
var accumulator: Double?
func someFunc() {
// I use `localAccumulator` and write `self.` for clarity.
if let localAccumulator = self.accumulator { // A
// `self.accumulator` is modified in this method
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(firstOperand: localAccumulator) // C
}
}
private func performPendingBinaryOperation() {
accumulator = pendingBinaryOperation.perform(with: accumulator)
}
...
}
这里的问题是B行改变了实例值self.accumulator的值,C行应该使用self.accumulator中存储的新值,但是它使用了outdated本地var localAccumulator 复制自 self.accumulator 的旧值。
通过调试器很容易找出逻辑错误。但后来我反思了自己的错误,并试图寻找一种方法来避免这种逻辑错误。
方法一:使用 nil 检查而不是可选绑定
if self.accumulator != nil { // A
// `self.accumulator` is modified in this method
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(firstOperand: self.accumulator!) // C
}
实际上,这里真正重要的是解包self.accumulator! 的力,它确保价值来自真实来源。使用 nil 检查而不是可选绑定可能会迫使我在 self.accumulator 上强制解包。
但在一些 Swift 风格指南(GitHub、RayWenderlich、LinkedIn)中,不鼓励强制展开。他们更喜欢可选绑定。
方法2:使用断言。
if localAccumulator = self.accumulator { // A
// `self.accumulator` is modified in this method
performPendingBinaryOperation() // B
assert(localAccumulator == self.accumulator) // D
pendingBinaryOperation = PendingBinaryOperation(firstOperand: localAccumulator) // C
}
我插入一个断言来检查localAccumulator 是否仍然等于self.accumulator。这有效,一旦self.accumulator 被意外修改,它将停止运行。但是很容易忘记添加这个断言行。
方法三:SwiftLint
为了找到检测这种错误的方法,我浏览了 SwiftLint 的所有规则,并对 SourceKitten(SwiftLint 的依赖项之一)。用 SwiftLint 检测这种错误似乎太复杂了,尤其是当我使这种模式更通用时。
一些类似的案例
案例1:保护可选绑定
func someFunc() {
guard let localAccumulator = self.accumulator { // A
return
}
// `self.accumulator` is modified in this method
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(firstOperand: localAccumulator) // C
}
在这种情况下,人类更难注意到错误,因为localAccumulator 具有比 if 可选绑定更广泛的保护可选绑定范围。
案例2:函数传参导致的值拷贝
// Assume that this function will be called somewhere else with `self.accumulator` as its argument, like `someFunc(self.accumulator)`
func someFunc(_ localAccumulator) {
// `self.accumulator` is modified in this method
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(firstOperand: localAccumulator) // C
}
在这种情况下,localAccumulator 在调用此函数时从 self.accumulator 复制,然后 self.accumulator 在 B 行中发生变化,C 行期望 self.accumulator 的新值,但从localAccumulator.
其实基本模式如下,
var x = oldValue
let y = x
functionChangingX() // assign x newValue
functionExpectingX(y) // expecting y is newValue, but it's oldValue
x~self.accumulator
y~localAccumulator
functionChangingX~performPendingBinaryOperation
functionExpectingX~PendingBinaryOperation.init
这个错误模式看起来很常见,我猜这个错误模式应该有一个名字。
不管怎样,回到我的问题,如何避免这种逻辑错误?
【问题讨论】: