在for循环中声明的变量。如何将其变为编译时错误?

9
今天,我调查了我们软件中的逻辑错误,并发现这与 VB.NET 线程变量在循环中的方式有关。假设我有以下代码:
    Dim numbers As New List(Of Integer) From {1, 2, 3, 4, 5}
    For Each number As Integer In numbers

        Dim isEven As Boolean

        If number Mod 2 = 0 Then
            isEven = True
        End If

        If isEven Then
            Console.WriteLine(number.ToString() & " is Even")
        Else
            Console.WriteLine(number.ToString() & " is Odd")
        End If

    Next

产生以下输出
1 is Odd
2 is Even
3 is Even
4 is Even
5 is Even

问题在于声明了isEven但没有赋值。 在这种情况下,正确的写法应该是dim isEven as Boolean = false,但我没有这样做。
在VB.NET中,声明在for循环内部的变量会保留其值供下一次迭代使用。这是设计上的决定:http://social.msdn.microsoft.com/Forums/en/vblanguage/thread/c9cb4c22-d40b-49ff-b535-19d47e4db38d,但这也是程序员容易掉进的陷阱。
然而,直到现在,我还没有意识到这个问题/行为。我们大部分的代码库都是C#,它不允许使用未初始化的变量,因此没有问题。
但是我们有一些遗留的VB.NET代码需要支持。
我认为我们团队中没有人曾经有过有目的地使用它的经历。如果我想要明确地在for循环中共享一个变量,我会在范围外声明它。
因此,在这种特殊情况下,最好的方法是生成一个警告,甚至是一个错误。但即使是使用Option Explicit / Option Strict也不会产生警告/错误。
有办法使这成为编译时错误,或者用FxCop检查吗?

这与VB.NET循环内线程变量的方式有关。不是的,你的代码就是错的,它没有遵守最基本的编程规则:初始化你的变量。 - GameAlchemist
5
OP并不是在寻求改进代码,他想将这个问题固定在编译器中,以便软件出厂时不会有缺陷。 - Chiwda
1
没错,自从那一次以后,我在我们的软件中再也没有发现过类似的代码片段,但我担心在我们的遗留代码中会有类似的陷阱。 - Jürgen Steinblock
VB不像C#那样工作,并不构成程序员的“危险陷阱”。然而,这确实需要专业能力。如果这是一个真正的问题,我建议注意“未初始化变量”的警告。 - RBarryYoung
1
重点是,在我发现这个问题之前,我认为变量在每次循环中都会是false,现在我知道了更好的方法并且可以避免这种情况。我的问题更像是“有没有一种方法可以找到类似的地方,以便我可以仔细检查可能的错误”。 - Jürgen Steinblock
显示剩余2条评论
3个回答

2

我认为我们开发团队中没有人有意使用这种方法。如果我想要在for循环内部的迭代之间显式地共享一个变量,我会将其声明在作用域之外。

我想,声明一个变量在循环内部的整个目的就是明确将其范围限制在该块内部。如果在编译时强制执行此操作,则会从语言中删除块级作用域。虽然在某些情况下,方法级别的作用域很合理,但无疑也可以提出一种对块级作用域重要性的论据。我认为你无法轻松地从语言中排除它,而不引入一些新的语法方法来使用它。此时,您正在进入重新设计VB.NET的领域 - 我不确定是否有简单的方法可以做到这一点。


2
我不想限制在循环内部声明变量,但我想限制在循环内部声明未初始化变量。这应该是可以的dim someValue as Integer = 0,但这样就不行了dim someValue as Integer,因为后者会引入潜在的危险,因为它会记住下一次迭代中最后分配的值。 - Jürgen Steinblock
是的,但部分功能来自于无需初始化它。将其声明在块内以使变量具有块作用域恰好涉及在每次迭代中运行该行。按照您所建议的去做将会移除具有块作用域变量和跨循环迭代传递值功能,这意味着从语言中删除没有其他语法揭示的功能。 - J...
事实是,VB.NET 有一些能力可以像 C# 一样改变编译时的行为:http://aurigroup.files.wordpress.com/2011/02/figure-4.png。所以我不想破坏任何东西,我想配置编译器。除此之外,如果不可能的话,FxCop 规则也是一个解决方案。但默认的 FxCop 并不认为这是一个问题。 - Jürgen Steinblock
那么真正的问题是“我该如何创建一个FxCop规则,以检测方法作用域下未初始化的变量?” - Peter Wone

0
如果您认为这可能是您的代码库或程序员的问题,请在您的编码风格中指定所有未初始化的变量在例程开始时声明。这一直是一个常见的风格指南(特别是针对VB),直到类型推断的普及。
当然,这并不能避免问题,只是使其更加明显。

0
看一下下面的代码。如果不允许声明而不初始化是一个编译器错误,那么这段代码将无法产生正确的输出(偶数的运行总和)。如果你强制我初始化'total'的值,那么这个方法永远都不可能正确。
Dim numbers As New List(Of Integer) From {1, 2, 3, 4, 5, 6, 8, 9, 10}
For Each number As Integer In numbers

    Dim total As Integer

    Dim isEven As Boolean = (number Mod 2 = 0)

    If isEven Then
        total += number
        Console.WriteLine("Running Total: {0}", total)
    End If
Next

不要添加错误,只需在代码中修复逻辑。我认为这不是一个特别危险的陷阱。大多数程序员应该能够识别这个问题,单元测试也应该有助于发现这些类型的问题。


2
大多数程序员都能识别这个问题 - 老实说,我没有。在你的例子中,我会在“dim numbers…”和“For Each”之间写入“dim total as integer”,因为我期望每次迭代时total被重置为0。你写道“不要添加错误,只需修复代码中的逻辑”,这正是我想做的。我已经修复了有问题的代码,但我想知道在我们的遗留代码中,这可能会引入哪些错误。 - Jürgen Steinblock
2
@SchlaWiener 有人同样可以主张,在复杂的代码中在 For Each 块之外声明 total,会引入能够在其预期使用的块的范围之外操纵 total 的风险。在块内声明它会强制其作用域被限制在 For Each 块内。在 total 不需要在 For Each 块之外使用的情况下,仅出于这个原因考虑,将其声明在块内而不是块外可能被认为是最佳实践。 - J...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接