欢迎光临散文网 会员登陆 & 注册

Mit_6301_2021_中文个人翻译_第一课_Static Checking

2023-08-16 01:31 作者:宋剑寒  | 我要投稿

# 第一章:Static Checking


**课程目标**


本节课有两个目标:


* 静态类型


* 优秀软件的三大特点


## 冰雹序列


冰雹序列从一个自然数 n 开始,如果 n 是偶数,那么它的下一个数为 n/2,如果 n 是奇数,那么下一个数为 3n + 1;冰雹序列会不停迭代直到这个数变为 1 为止。


以下是一些冰雹序列的例子。


``````

2, 1

3, 10, 5, 16, 8, 4, 2, 1

4, 2, 1

2^n, 2^(n-1) , … , 4, 2, 1

5, 16, 8, 4, 2, 1

7, 22, 11, 34, 17, 52, 26, 13, 40, …? (尚未结束)

``````


由于存在奇数的 3n + 1 法则,冰雹序列在最终停止之前会反复的上升下降。但是根据推测,任意的自然数 n 起始的冰雹序列,最终都会达到 1 而停止,但这仍只是个猜想,没有严格的证明。另外一提,冰雹序列之所以叫做冰雹序列,是因为冰雹在云层中积攒重量时,会在云层中上下跳动,直到它积攒了足够的重量能够落到地上去。


## 如何计算冰雹序列


这里给出一些计算冰雹序列的 Java 代码和 Python 代码,大家可以比较以下它们的区别


```java

// Java

int n = 3;

while (n != 1) {

    System.out.println(n);

    if (n % 2 == 0) {

        n = n / 2;

    } else {

        n = 3 * n + 1;

    }

}

System.out.println(n);

   

```


```python

# Python

n = 3

while n != 1:

    print(n)

    if n % 2 == 0:

        n = n / 2

    else:

        n = 3 * n + 1

       

print(n)

```


这里有一些值得注意的点:


* Java 和 Python 的基础语法是十分相似的:


  比如 ``while`` 和 ``if`` 的使用是是非相似的


* Java 在每行的某位需要使用分号作为结尾


* Java 在使用 ``if`` 和 ``while`` 做判断的时候,需要把条件用括号括起来


* Java 使用中括号而不是缩进来标识语句块。Java 不关注你是否使用空格来区分语句直接的关系,但你还是应当使用缩进来表明语句块之间的关系。因为编程也是一种交流,你不止需要面对编译器,还需要面对阅读你代码的普通人。


  而人,是需要这些缩进的,我们以后还会提到这点。


## 类型


Java 和 Python 在语法上的最重要的区别在于变量 ``n`` 的声明,在 Java 中我们声明了它的类型 ``int``


**类型** 涵盖了从属这些类型的值和定义在这些值上的运算


Java 有几种基本数据类型,以下是几个例子:


* ``int`` (类似于 5 或者 -200 的整数,但是它有其上下限 ±2^31,或者说 ±20亿)

* ``long`` (用于更大的整数,±2^63)

* ``boolean`` (只有两个值,``true`` 和 ``false``)

* ``double`` (用于浮点数)

* ``char`` (用于单个字母,比如 ``A`` 和 ``$`` )


Java 还有引用类型,比如:


* ``String`` 表示 ``char`` 的序列,就像 ``python`` 的 字符串 一样

* ``BigInteger`` 表示任意大小的整数,就像 ``python`` 中的整数一样


在 Java 中,我们习惯上用小写来表示基本数据类型,用大写来表示引用类型


定义在这些类型上的运算,实际上是一些接受输入,产生输出的函数(有时会改变传入的值),运算使用时的语法千差万别,但是我们仍旧视它们为函数。以下是一些 Python 或者 Java 中运算的使用示例:


* 运算符:``a + b`` 会调用操作 ``+ : int x int -> int`` 。(``+`` 是这个运算的名字,``int x int`` 描述了两个输入,箭头的右边的 ``int`` 描述了输出。)

* 实例方法:``bigint1.add(bigint2)`` 调用运算:``add: BigInteger x BigInteger -> BigInteger`` 。

* 类方法:``Math.sin(theta)`` 调用运算 ``sin: double -> double`` 这里 ``Math`` 不是一个对象,而是一个类,这个类中包含了 ``sin`` 函数。


我们对比一下 Java 中的 ``str.length()`` 和 Python 中的 ``len(str)`` 。它们的功能都是输入一个字符串,返回字符串的长度,只在语法上有一点区别。


有一些操作符是重载过的,这使得它们可以在不同的数据类型中使用而不需要改变形式。Java 中用于基本数据类型计算的操作符都是经过重载的,比如``+, -, *, /`` 。Java 中方法也是可以被重载的,大多数的编程语言都可以实现不同程度的重载。


## 静态类型


Java 是 **静态类型语言**:所有变量的类型在编译期间(程序运行前)就已经确定,因此编译器也可以通过变量的类型来确定变量表达式结果的类型。如果 ``a`` 和 ``b`` 被声明为 ``int`` , 编译器就会认为 ``a + b`` 也是 ``int`` 类型。Eclipse 在你还在编写程序的同时就会进行类型检查,所以你能够在程序编写阶段就发现很多错误。


在 **动态类型语言** 中,这种类型检查会被推迟倒程序运行时执行,Python 就是动态类型语言。


**静态检查**意味着在编译时期就检查bugs。Bugs 是编程的万恶之首。本课程的很多部分都在试图教会你如何在代码中消除 bug,而静态检查就是你最先看到的部分。静态类型可以减少你程序中的一大类错误:准确的说,就是数据类型的不匹配。如果你的代码像下面一样:


```java

"5" * "6"

```


这行代码尝试将两个字符串相乘,静态类型语言就会发现这个错误,并且在你还在编写程序的时候就提出来。


## 在动态类型语言中支持静态类型


尽管 Python 是一个动态类型语言,Python 3.5 以及更新的版本已经支持使用 [type hints](https://peps.python.org/pep-0484/) 来在代码中支持静态类型,代码示例:


```python

# Python function declared with type hints

def hello(name:str)->str:

    return 'Hi, ' + name

```


可以使用像 [Mypy](https://mypy-lang.org/) 检查器来在你还没有运行程序之前来检查类型错误


在动态类型语言中添加静态类型反应了软件工程领域一个非常普遍的认知:静态类型对于开发和维持一个大型软件系统是非常重要的。事实上本书的剩余部分或者说整个课程都在致力于说明这一认知的理由。


与 Java 这种从诞生之初就是静态类型的语言不同,在动态类型语言中添加静态类型可以实现一种新的编程类型:渐进类型。也就是代码中的一部分是静态类型声明,一部分是动态类型声明。渐进类型能够让你从一个小的不完整的原型系统开始,开发为一个大型的稳固的可维护的系统的过程中提供更加顺畅的体验。


## 静态检查、动态检查、不检查


让我们来仔细思考一下以下三种编程语言能够提供给我们的自动检查:


* **静态检查** :在程序还没有运行之前就能够发现 bug

* **动态检查** :在程序执行时才发现 bug

* **不检查** :该编程语言完全不检查任何 bug,你必须自己找到 bug,或者接受一个错误的答案


无需多言,静态检查优于动态检查,动态检查又优于不检查


以下是一些经验之谈来表明你能在不同的检查类型下能够发现哪些 bug:


**静态检查**能够发现:


* 语法错误,比如额外的标点符号或者拼写错误。即使是动态类型的语言也会检查这种类型的错误,比如 Python 在你运行程序之前就检查缩进错误。

* 拼写错误比如``Math.sine(2)`` (正确的拼写是 ``sin`` )

* 错误的参数:``Math.sin(30, 20)`` 

* 错误的参数类型:``Math.sin("30")`` 

* 错误的返回类型:函数声明的返回类型是 ``int`` ,而实际返回的却是 ``return "30"`` 


**动态检查**能够发现:


* 非法参数值:对于整数相除的表达式 ``x / y`` 只有当 ``y`` 为 0 的时候,才会出现除零错误,当 ``y`` 不为 0 时,这个表达式的值是正确且确定的。所以在这个例子中,除零错误是一个动态错误,而不是一个静态错误。

* 不合法的类型转换:有些特定的值是不能转换为其他数据类型的。比如:```Integer.valueOf("hello")`` 是一个动态错误,因为 ``hello`` 不能被转换为一个十进制整数。``Integer.valueOf("8000000000")`` 也是一个动态错误,因为 80 亿超出了 ``int`` 类型能够接收的范围。

* 超出索引范围,例如在字符串索引时使用负数或者过大的索引

* 调用实例方法时,对象引用为 ``null`` ,(``null`` 和 Python 中的 ``None`` 类似)


静态检查可以检测跟变量类型有关的错误,但是通常不能检测这个变量类型特定值相关的错误。静态检查保证了变量具有其变量类型的值,但是具体的值只有等到程序运行期间才得以知晓。所以如果某个错误只跟这个类型的特定值有关,比如除零错误,那么编译器就不会找到静态错误。


动态检查倾向于找到那些具体值所引发的错误。


## 你或许不知道的:基本数据类型并不是真正的数学数字


Java 和 其他一些编程语言中有一个经典的陷阱:它们的基本数据数字类型在极端情况下并不像我们在日常数学中使用的数字一样。这会导致一些应该被动态检查的错误,没有被检测出。


* 整数除法:``5 / 2`` 并不会返回一个小数,它会返回一个被截断的整数。这常常会导致我们的程序产生错误的答案,并且程序还不会检测出动态错误。


* 整数溢出:``int`` 和 ``long`` 实际上是有限的整数集,具有上限和下限。当你向这两种数据类型中填充一个过大的正整数或者过小的负整数时会发生什么呢?答案是产生溢出,并且是悄悄的溢出,它会返回一个类型范围内的值,但不是正确的值。


  ```java

  int a = 2147483647;

  a = fun(a);

  public static int fun(int a) {

      return a + 1;

  }

  ```


* 浮点类型中的特殊值:浮点类型像 ```double``` 拥有几种特殊值。```NaN``` (Not a Number),```POSITIVE_INFINITY``` , ```NEGATIVE_INFINITY``` 。当你在```double``` 类型上做一些操作时,比如除零或者对负数开根号,你将不会得到一个动态错误,而是获得以上的几种特殊值。如果你把这些值当作是中间值进行计算,你最终可能会得到一个奇怪的答案。


## 练习


接下来我们来做一些练习来巩固知识,以下这些有 bug 的代码,它们的错误会被静态检查捕获还是动态检查还是捕获不到呢?


```java

int n = -5;

if (n) {

  System.out.println("n is a positive integer");

}

```


```java

int big = 200000; // 200,000

big = big * big;  // big should be 40 billion now

```


```java

double probability = 1/5;

```


```java

int sum = 0;

int n = 0;

int average = sum/n;

```


```java

double sum = 7;

double n = 0;

double average = sum/n;

```


Mit_6301_2021_中文个人翻译_第一课_Static Checking的评论 (共 条)

分享到微博请遵守国家法律