静态/强类型和重构

时间:2009-05-19 00:17:44

标签: refactoring strong-typing static-typing weak-typing

在我看来,静态/强类型编程语言最有价值的是它有助于重构:如果/当你更改任何API时,编译器会告诉你这个更改已经破坏了什么。

我可以想象用运行时/弱类型语言编写代码......但是如果没有编译器的帮助我无法想象重构,我无法想象在没有重构的情况下编写成千上万行代码。

这是真的吗?

5 个答案:

答案 0 :(得分:13)

我认为在检查类型时会考虑如何检查它们。运行时输入不一定很弱。

静态类型的主要优点正是您所说的:它们是详尽无遗的。您可以确信所有调用站点都符合类型,只需让编译器执行此操作即可。

静态类型的主要限制是它们在可以表达的约束中受到限制。这取决于语言,大多数语言具有相对简单的类型系统(c,java),而其他语言具有极其强大的类型系统(haskell,cayenne)。

由于这种限制,它们本身并不充分。例如,在java类型中或多或少地限制为检查类型名称匹配。这意味着你想要检查的任何约束的含义必须被编码成某种命名方案,因此java代码常见的过多的间接和锅炉板。 C ++稍微好一些,因为模板允许更具表现力,但是不能接近使用依赖类型可以做的事情。我不确定更强大的类型系统的缺点是什么,但显然必须有一些或更多人在工业中使用它们。

即使您使用的是静态类型,也可能无法表达您所关注的所有内容,因此您也需要编写测试。无论静态打字是否能比你在样板文件中所需要的更省力,这种争论已经持续了很长时间,并且我认为对所有情况都没有简单的答案。

至于你的第二个问题:

我们如何安全地重新考虑运行时类型的语言?

答案是测试。您的测试必须涵盖所有重要的案例。工具可以帮助您衡量测试的详尽程度。覆盖率检查工具可以让您知道测试是否涵盖了更多代码。测试变异工具(jester,heckle)可以让您知道您的测试是否在逻辑上不完整。验收测试让您知道您所写的匹配要求,最后回归和性能测试确保产品的每个新版本都保持最后的质量。

进行适当的测试而不是依赖精心设计的类型间接的一个好处是调试变得更加简单。在运行测试时,您会在测试中获得特定的失败断言,这些断言清楚地表达了他们正在做的事情,而不是迟钝的编译器错误陈述(想想c ++模板错误)。

无论您使用什么工具:编写您自信的代码都需要付出努力。它很可能需要编写大量测试。如果对错误的惩罚非常高,例如航空航天或医疗控制软件,您可能需要使用正式的数学方法来证明您的软件的行为,这使得这种开发非常昂贵。

答案 1 :(得分:5)

我完全同意你的观点。动态类型语言应该擅长的灵活性实际上是使代码很难维护的原因。真的,如果在不实际更改代码的情况下以非平凡的方式更改数据类型,那么程序会继续工作吗?

同时,您可以检查传递的变量的类型,如果它不是预期的类型,则会以某种方式失败。你仍然需要运行你的代码来根除这些情况,但至少会有一些东西告诉你。

我认为Google的内部工具实际上是在进行编译,可能会对其Javascript进行类型检查。我希望我有这些工具。

答案 2 :(得分:2)

首先,我是一个本地的Perl程序员,所以一方面我从未使用静态类型的网络进行编程。 OTOH我从来没有和他们一起编程,所以我不能说他们的好处。我能说的是它重构的意思。

我没有发现缺少静态类型是重构的问题。我发现问题的是缺少重构浏览器。动态语言存在的问题是,在实际运行代码之前,您实际上并不知道代码实际上要做什么。 Perl比大多数人都要多。 Perl还有一个非常复杂,几乎不可解决的语法问题。结果:没有重构工具(尽管they're working very rapidly on that)。最终的结果是我必须手工重构。这就是引入错误的原因。

我有测试来抓住他们......通常。我发现自己常常面对一堆未经测试且近似无法测试的代码,其中鸡/蛋问题必须重构代码以便对其进行测试,但必须对其进行测试才能对其进行重构。伊克。在这一点上,我必须写一些非常愚蠢的,高级别的“程序输出与之前相同的事情”,以确保我没有破坏某些测试。

静态类型,如Java或C ++或C#中所设想的,实际上只能解决一小类编程问题。它们保证您的接口通过正确的标签传递数据位。但仅仅因为你得到一个Collection并不意味着Collection包含你认为它所做的数据。因为你得到一个整数并不意味着你得到了正确的整数。您的方法采用User对象,但用户是否已登录?

经典示例:public static double sqrt(double a)signature for the Java square root function。平方根不适用于负数。签名在哪里?它没有。更糟糕的是,它在哪里说这个功能甚至做了什么?签名只说明它需要什么类型和返回的内容。它没有说明两者之间发生的事情以及有趣的代码所在的地方。有些人试图通过使用design by contract来捕获完整的API,这可以概括地描述为嵌入函数输入,输出和副作用(或缺少)的运行时测试...但这是另一个节目。

API不仅仅是函数签名(如果不是,你不需要Javadocs中的所有描述性散文),重构甚至比仅更改API还要多。

静态类型,静态编译,非动态语言的最大重构优势使您能够编写重构工具来为您执行相当复杂的重构,因为它知道对方法的所有调用都在哪里。我很羡慕IntelliJ IDEA

答案 3 :(得分:1)

我认为重构超出了编译器可以检查的范围,即使在静态类型的语言中也是如此。重构只是在不影响外部行为的情况下改变程序内部结构。即使在动态语言中,仍然存在可以发生和测试的事情,您只需从编译器中获得一些帮助。

答案 4 :(得分:1)

在C#3.0中使用 var 的一个好处是,可以经常在不破坏任何代码的情况下更改类型。类型仍需要看起来相同 - 必须存在具有相同名称的属性,具有相同或类似签名的方法仍然必须存在。但即使不使用像ReSharper这样的东西,你也可以真正改变为一种非常不同的类型。