软件构造 1: 静态检查

本文介绍了静态类型检查的概念及其在编程语言中的应用,强调了它在编译时发现错误的重要性。静态类型能够防止类型相关的bug,而动态检查则在运行时发现错误。文章还讨论了静态和动态检查各自的优点,以及静态检查如何帮助提高代码的可读性和可维护性。此外,文中提到了静态类型语言中的一些陷阱,如原始类型的行为,以及如何利用不变性原则来增强代码的稳定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

静态类型

Java是一种静态类型的语言。所有变量的类型在编译的时候就已经知道了(程序还没有运行),所以编译器也可以推测出每一个表达式的类型。例如,如果abint类型的,那么编译器就可以知道a+b的结果也是int类型的。事实上,Eclipse在你写代码的时候就在做这些检查,所以你就能够在编辑的同时发现这些问题。

动态类型语言中(例如Python),这种类型检查是发生在程序运行的时候

静态类型是静态检查的一种——检查发生在编译的时候。本课程的一个重要目标就是教会你避免bug的产生,静态检查就是我们知道的第一种方法。其中静态类型就阻止了一大部分和类型相关的bug——确切点说,就是将操作符用到了不对应的类型对象上。例如,如果你进行下面这个操作,试图将两个字符串进行算术乘法:

    "5" * "6"

那么静态类型检查就会在你编辑代码的时候发现这个bug,而不是等到你编译后运行程序的时候(编译也不通过)。

静态检查、动态检查、无检查

编程语言通常能提供以下三种自动检查的方法:

  • 静态检查: bug在程序运行前发现
  • 动态检查: bug在程序运行中发现
  • 无检查: 编程语言本身不帮助你发现错误,你必须通过特定的条件(例如输出的结果)检查代码的正确性。

很明显,静态检查好于动态检查好于不检查。

这里有一些“经验法则”,告诉你这静态和动态检查通常会发现什么bug:

静态检查 :

  • 语法错误,例如多余的标点符号或者错误的关键词。即使在动态类型的语言例如Python中也会做这种检查:如果你有一个多余的缩进,在运行之前就能发现它。
  • 错误的名字,例如Math.sine(2). (应该是 sin.)
  • 参数的个数不对,例如 Math.sin(30, 20).
  • 参数的类型不对 Math.sin("30").
  • 错误的返回类型 ,例如一个声明返回int类型函数return "30";

动态检查 :

  • 非法的变量值。例如整型变量x、y,表达式x/y 只有在运行后y为0才会报错,否则就是正确的。
  • 无法表示的返回值。例如最后得到的返回值无法用声明的类型来表示。
  • 越界访问。例如在一个字符串中使用一个负数索引。
  • 使用一个null对象解引用。(null相当于Python中的None

静态检查倾向于类型错误 ,即与特定的值无关的错误。正如上面提到过的,一个类型是一系列值的集合,而静态类型就是保证变量的值在这个集合中,但是在运行前我们可能不会知道这个值的结果到底是多少。所以如果一个错误必须要特定的值来“触发”(例如除零错误和越界访问),编译器是不会在编译的时候报错的。

与此相对的,动态类型检查倾向于特定值才会触发的错误。

原始类型并不是真正的数字!

在Java和许多其他语言中存在一个“陷阱”——原始数据类型的对象在有些时候并不像真正的数字那样得到应有的输出。结果就是本来应该被动态检查发现的错误没有报错。例如:

  • 整数的除法5/2并不会返回一个小数,而是一个去掉小数部分的整数对象,因为除法操作符对两个整数对象运算后的结果还是整数,而整数对象是无法表示5/2的精确值的(而我们期望它会是一个动态检查能发现的错误)。
  • 整形溢出: int 和 long类型的值的集合是一个有限集合——它们有最大的值和最小的值,当运算的结果过大或者过小的时候我们就很可能得到一个在合法范围内的错误值。
  • 浮点类型中的特殊值:在浮点类型例如double中有一些不是数的特殊值:NaN ( “Not a Number”), POSITIVE_INFINITY (正无穷), and NEGATIVE_INFINITY (负无穷).当你对浮点数进行运算的时候可能就会得到这些特殊值(例如除零或者对一个负数开更号),如果你拿着这些特殊值继续做运算,那你可能就会得到一个意想不到结果(译者注:例如拿NaN和别的数进行比较操作永远是False) 。

变化的值 vs. 可被赋值的改变

“快照图”(snapshot diagrams)可以来辨别修改一个变量和修改一个值的区别。当你给一个变量赋值的时候,你实际上是在改变这个变量指向的对象(值也不一样)。

而当你对一个可变的值进行赋值操作的时候——例如数组或者列表——你实际上是在改变对象本身的内容。

变化是“邪恶”的,好的程序员会避免可改变的东西,因为这些改变可能是意料之外的。

不变性(Immutability)是我们这门课程的一个重要设计原则。不变类型是指那些这种类型的对象一旦创建其内容就不能被更改的类型(至少外部看起来是这样,我们在后面的的课程中会说一些替代方案)。思考一下在上面的代码中哪一些类型是可更改类型,哪一些不是?(译者注:例如int就是不变的,List就是可变的,给int类型的对象赋值就会让它指向一个新的对象)

Java也给我们提供了不变的索引:只要变量被初始化后就不能再次被赋值了——只要在声明的时候加上final :

final int n = 5;

如果编译器发现你的final变量不仅仅是在初始化的时候被“赋值”,那么它就会报错。换句话说,final会提供不变索引的静态检查。

正确的使用final是一个好习惯,就好像类型声明一样,这不仅会让编译器帮助你做静态检查,同时别人读起来也会更顺利一些。

总结

我们今天主要介绍的思想为静态代码检查, 下面是该思想如何和我们的目标进行关联

  • 远离bug. 静态代码检查可以通过捕捉类型错误等其他BUG帮助我们在运行代码之前就发现它们
  • 易读性. 它可以帮助我们理解, 因为所有的类型在代码中被明确定义 (译者注: 相比于 Python/PHP 这类动态变量类型的语言)
  • 可改动性. 静态代码检查可以在你在修改你的代码的时候定位出也需要被修改的地方, 例如: 当你改变一个变量的类型或者名称的时候, 编译器立即就会在所有使用到这个变量的地方显示错误, 提示你也需要更新它们。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值