【问题标题】:How can I prevent this kind of logic error? (var x changes between its value is copied to another var y and the use of var y, so that y is outdated)如何防止这种逻辑错误? (var x 在其值被复制到另一个 var y 和使用 var y 之间发生变化,从而使 y 过时)
【发布时间】: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

这个错误模式看起来很常见,我猜这个错误模式应该有一个名字。

不管怎样,回到我的问题,如何避免这种逻辑错误?

【问题讨论】:

    标签: swift lint cs193p


    【解决方案1】:

    这个例子显示了我对你的问题的理解:

    var name: String? = "Mike"
    
    func doSomething() {
    
        // Check if "name" is set (not nil)
        if let personName = name {
    
            addLastNameToGlobalName() // modifies global var "name"
    
            // 
            // Here, you want to use the updated "name" value. As you mention, at this point, 
            // "name" could/might/should be different than "personName" (it could
            // even be nil).
            // 
            greet(person: ???) // use "name"? (which is optional), "personName"?, or what?
    
        }
    }
    

    对我来说,一般方法是这里的问题。你是这样的事实:

    1. 检查全局可选值是否不为零
    2. 调用更改此全局可选值的函数
    3. 想要将更新后的全局可选值用作非可选值

    在问候此人的方式/设计上做一个简单的改变,就可以避免你提到的那种“逻辑错误”。

    示例 1:

    var name: String? = "Mike"
    
    func doSomething() {
    
        // Check if "name" is set (not nil)
        if let personName = name {
            greet(person: fullName(for: personName))
        }
    }
    

    示例 2:

    var name: String? = "Mike"
    
    func doSomething() {
    
        // Check if "name" is set (not nil)
        if let personName = name {
            let fullName = addLastNameToGlobalName() // modifies global var "name" and returns the new value
            greet(person: fullName) 
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-31
      • 1970-01-01
      • 1970-01-01
      • 2012-06-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多