麻省理工18年春軟件構造課程閱讀01“靜態檢查”


**本文內容來自[MIT_6.031_sp18: Software Construction](http://web.mit.edu/6.031/www/sp18/)課程的Readings部分,采用[CC BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)協議。**

由於我們學校(哈工大)大二軟件構造課程的大部分素材取自此,也是推薦的閱讀材料之一,於是打算做一些翻譯工作,自己學習的同時也能幫到一些懶得看英文的朋友。另外,該課程的閱讀資料中有許多練習題,但是沒有標准答案,所給出的答案均為譯者所寫,有錯誤的地方還請指出。




譯者:李秋豪 王一航

審校:李秋豪

V1.0 Thu Mar 1 00:49:04 CST 2018


Reading 1: 靜態檢查

今天課程的目標:

  • 學習靜態類型
  • 了解好的軟件應該具有的三個屬性

冰雹序列

作為一個運行示例,我們先來了解一下“冰雹序列”,它是這樣定義的:從正整數n開始,如果n是偶數,則下一個數是n/2,否則下一個數是3n+1,直到n等於1。這里有幾個例子:

2, 1
3, 10, 5, 16, 8, 4, 2, 1
4, 2, 1
2n, 2n-1 , … , 4, 2, 1
5, 16, 8, 4, 2, 1
7, 22, 11, 34, 17, 52, 26, 13, 40, …? (會停止嗎?)

由於存在3n+1這種變化,所以序列元素的大小可能會忽高忽低——這也是“冰雹序列”名稱的來歷,冰雹在落地前會在雲層中忽上忽下。那么所有的序列都會最終“落地”變到1嗎?(這個猜想稱為考拉茲猜想 ,現在還沒有一個好的解決方法。)


計算冰雹序列

為了計算冰雹序列里的元素,我們可以在java和python中分別這樣寫:

// 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
n = 3
while n != 1:
    print(n)
    if n % 2 == 0:
        n = n / 2
    else:
        n = 3 * n + 1
print(n)

有些東西值得一提(譯者注:MIT大一學的是python,所以這里談了一下java和python語法上的區別,懂的讀者可以略過):

  • Java和Python的基本語法很相似,例如whileif
  • Java在每一個句子結束時要求以分號作為結尾。這看起來有些麻煩,但也給了你很多自由以便安排代碼——例如你可以將一行代碼寫成兩行然后以分號結束。
  • Java在使用 ifwhile的時候,其中的條件判斷需要用括號括起來。
  • Java使用花括號將一個語句塊分割開來——python是以縮進作為區分,所以你寫一些別的空格符也是可以的。但是編程也是一種交流,你不僅要和編譯器交流,還要和別的程序員交流,所以縮進也是必要的,我們在后面的課中會專門講這個。

類型

在上面的代碼中,Python和Java最大的不同就是Java需要指定變量n的類型:int

類型是一些值的集合,以及這些值對應的操作。

例如下面這5種常用的原始類型

  • int 例如5和-200這樣的整數,但是其范圍有限制,大概在±20億)
  • long (比int更大范圍的整數)
  • boolean對或錯這兩個值)
  • double (浮點數,其表示的是實數的子集)
  • char (單個字符例如 'A''$'

Java也有對象類型 ,例如:

  • String 表示一串連續的字符。
  • BigInteger 表示任意大小的整數。

從Java的傳統來說,原始類型用小寫字母,對象類型的起始字母用大寫。

操作符是一些能接受輸入並輸出結果的功能。他們的語法各有區別,Java中常見的有下面這三種:

  • 前綴、中綴、后綴操作符. 例如, a + b 調用這樣一種操作(映射) + : int × int → int

    ( + 是這個操作符的名字, int × int 描述了這兩個輸入, 最后的 int 描述的了輸出)

  • 一個對象的方法. 例如, bigint1.add(bigint2) 調用這樣一種操作(映射) add: BigInteger × BigInteger → BigInteger.

  • 一個函數. 例如: Math.sin(theta) 調用這樣一種操作(映射) sin: double → double. 注意, Math 不是一個對象,它是一個包含sin函數的類。

有一些操作符可以對不同類型的對象進行操作,這時我們就稱之為可重載overloaded),例如Java中的算術運算符 +, -, *, / 都是可重載的。一些函數也是可重載的。大多數編程語言都有不容程度的重載性。


靜態類型

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的精確值的(而我們期望它會是一個動態檢查能發現的錯誤)。
  • 整形溢出: intlong類型的值的集合是一個有限集合——它們有最大的值和最小的值,當運算的結果過大或者過小的時候我們就很可能得到一個在合法范圍內的錯誤值。
  • 浮點類型中的特殊值:在浮點類型例如double中有一些不是數的特殊值:NaN ( “Not a Number”), POSITIVE_INFINITY (正無窮), and NEGATIVE_INFINITY (負無窮).當你對浮點數進行運算的時候可能就會得到這些特殊值(例如除零或者對一個負數開更號),如果你拿着這些特殊值繼續做運算,那你可能就會得到一個意想不到結果(譯者注:例如拿NaN和別的數進行比較操作永遠是False) 。

閱讀小練習

下面這些代碼都有各自的bug,請你判斷它們是靜態錯誤還是動態錯誤還是無報錯,但是得到錯誤的結果。

1

int n = 5;
if (n) {
  n = n + 1;
}
  • [x] 靜態錯誤

  • [ ] 動態錯誤

  • [ ] 無報錯,但是得到錯誤的結果

2

int big = 200000; // 200,000
big = big * big;  // big should be 40 billion now
  • [ ] 靜態錯誤

  • [ ] 動態錯誤

  • [x] 無報錯,但是得到錯誤的結果

3

double probability = 1/5;
  • [ ] 靜態錯誤

  • [ ] 動態錯誤

  • [x] 無報錯,但是得到錯誤的結果

4

int sum = 0;
int n = 0;
int average = sum/n;
  • [ ] 靜態錯誤

  • [x] 動態錯誤

  • [ ] 無報錯,但是得到錯誤的結果

5

double sum = 7;
double n = 0;
double average = sum/n;
  • [ ] 靜態錯誤

  • [ ] 動態錯誤

  • [x] 無報錯,但是得到錯誤的結果

Mr.Xu: 浮點數除零為Infinity


譯者注:下面的四小節講的是Java語言本身的一些性質,對Java很熟悉的同學可以跳過或略讀。


數組和聚集類型

現在讓我們把“冰雹序列”的結果存儲在數據結構中而不僅僅是輸出。在Java中有兩種常用的線性存儲結構:數組和列表。

數組是一連串類型相同的元素組成的結構,而且它的長度是固定的(元素個數固定)。例如,我們聲明一個int類型的數組:

int[] a = new int[100];

對於數組,常用的操作符有下:

  • 索引其中的一個元素: a[2]
  • 賦予一個元素特定的值: a[2]=0
  • 求這個數組的長度: a.length (注意和 String.length() 的區別—— a.length 不是一個類內方法調用,你不能在它后面寫上括號和參數)

下面是我們利用數組寫的第一個求“冰雹序列”的代碼,它存在一些bug:

int[] a = new int[100];  // <==== DANGER WILL ROBINSON
int i = 0;
int n = 3;
while (n != 1) {
    a[i] = n;
    i++;  // very common shorthand for i=i+1
    if (n % 2 == 0) {
        n = n / 2;
    } else {
        n = 3 * n + 1;
    }
}
a[i] = n;
i++;

相信很快你就能發現錯誤:幻數100?(譯者注:幻數是指那些硬編碼的數值)那如果n產生的“冰雹序列”非常長呢?像這樣的bug稱為越界訪問,在Java中能夠被動態檢查檢測出來,但是在C和C++這樣的語言中則會造成 緩沖區溢出 (能通過編譯),這也是很多漏洞的來源。

解決方法是使用List類型。列表類型是一個長度可變的序列結構。我們可以這樣聲明列表:

List<Integer> list = new ArrayList<Integer>();

常用的操作符有下:

  • 索引一個元素: list.get(2)
  • 賦予一個元素特定的值: list.set(2, 0)
  • 求列表的長度: list.size()

這里要注意List是一個接口,這種類型的對象無法直接用new來構造,必須用能夠實現List要求滿足的操作符的方法來構造。我們會在后來講抽象數據類型的時候具體將價格這個。ArrayList是一個實類型的類(concrete type),它提供了List操作符的具體實現。當然,ArrayList不是唯一的實現方法(還有LinkedList 等),但是是最常用的一個)。你可以在Java API的文檔里找到很多這方面的信息(Google Java 8 API,這里的API指“應用程序接口”,它會告訴你Java里面實現的很多有用的類和方法)。

另外要注意的是,我們要寫List<Integer> 而不是 List<int>.因為List只會處理對象類型而不是原始類型。在Java中,每一個原始類型都有其對應的對象類型(原始類型使用小寫字母名字,例如int,而對象類型的開頭字母大寫,例如Integer)。當我們使用尖括號參量化一個類型時,Java要求我們使用對象類型而非原始類型。在其他的一些情況中,Java會自動在原始類型和對等的對象類型之間相轉換。例如在上面的代碼中我們可以使用Integer i = 0

下面是用列表寫的“冰雹序列”的實現:

List<Integer> list = new ArrayList<Integer>();
int n = 3;
while (n != 1) {
    list.add(n);
    if (n % 2 == 0) {
        n = n / 2;
    } else {
        n = 3 * n + 1;
    }
}
list.add(n);

這樣實現不僅看起來簡潔,更重要的是安全,因為列表會自動擴充它自己以滿足新添加的元素(當然,直到你的內存不夠用為止)


迭代

對於在一個序列結構(例如列表和數組)遍歷元素,Java和Python的寫法差不多:

// find the maximum point of a hailstone sequence stored in list
int max = 0;
for (int x : list) {
    max = Math.max(x, max);
}

Math.max() 是一個Java API提供的方便的函數。你可以在Google中搜索“java 8 Math”獲得關於Math這個庫的一些詳細信息。


方法

在Java中,聲明通常必須在一個方法中,而每個方法都要在一個類型中,所以寫“冰雹序列”程序最簡單可以這么寫:

public class Hailstone {
    /**
     * Compute a hailstone sequence.
     * @param n  Starting number for sequence.  Assumes n > 0.
     * @return hailstone sequence starting with n and ending with 1.
     */
    public static List<Integer> hailstoneSequence(int n) {
        List<Integer> list = new ArrayList<Integer>();
        while (n != 1) {
            list.add(n);
            if (n % 2 == 0) {
                n = n / 2;
            } else {
                n = 3 * n + 1;
            }
        }
        list.add(n);
        return list;
    }
}

下面介紹一些新的東西。

public意味着任何在你程序中的代碼都可以訪問這個類或者方法。其他的類型修飾符,例如private ,是用來確保程序的安全性的——它保證了可變類型不會被別處的代碼所修改。我們會在后面的課程中詳細提到。

static意味這這個方法沒有self這個參數——Java會隱含的實現它,所以你不會看到這個參數。靜態的方法不能通過對象來調用,例如List add() 方法 或者 String length()方法,它們要求先有一個對象。靜態方法的正確調用應該使用類來索引,例如:

Hailstone.hailstoneSequence(83)

另外,記得在定義的方法前面寫上注釋。這些注釋應該描述了這個方法的功能,輸入輸出/返回,以及注意事項。記住注釋不要寫的啰嗦,而是應該直切要點,簡潔明了。例如在上面的代碼中,n是一個整型的變量,這個在聲明的時候int已經體現出來了,就不需要進行注釋。但是如果我們設想的本意是n不能為負數,而這個編譯器(聲明)是不能檢查和體現出來的,我們就應該注釋出來,方便閱讀理解和修改。

這些東西我們會在后面的課程中詳細介紹,但是你現在就要開始試着正確使用他們。


變化的值 vs. 可被賦值的改變

在下一篇閱讀資料中我們會介紹“快照圖”(snapshot diagrams),以此來辨別修改一個變量和修改一個值的區別。當你給一個變量賦值的時候,你實際上是在改變這個變量指向的對象(值也不一樣)。

而當你對一個可變的值進行賦值操作的時候——例如數組或者列表——你實際上是在改變對象本身的內容。

變化是“邪惡”的,好的程序員會避免可改變的東西,因為這些改變可能是意料之外的。

不變性(Immutability)是我們這門課程的一個重要設計原則。不變類型是指那些這種類型的對象一旦創建其內容就不能被更改的類型(至少外部看起來是這樣,我們在后面的的課程中會說一些替代方案)。思考一下在上面的代碼中哪一些類型是可更改類型,哪一些不是?(譯者注:例如int就是不變的,List就是可變的,給int類型的對象賦值就會讓它指向一個新的對象)

Java也給我們提供了不變的索引:只要變量被初始化后就不能再次被賦值了——只要在聲明的時候加上final

final int n = 5;

如果編譯器發現你的final變量不僅僅是在初始化的時候被“賦值”,那么它就會報錯。換句話說,final會提供不變索引的靜態檢查。

正確的使用final是一個好習慣,就好像類型聲明一樣,這不僅會讓編譯器幫助你做靜態檢查,同時別人讀起來也會更順利一些。

hailstoneSequence方法中有兩個變量n和list,我們可以將它們聲明為final嗎?請說明理由。(譯者注:n不行,list可以。因為我們需要改變n指向的對象,而List對象本身是可以更改的,我們也不需要改變list對應的對象)

public static List<Integer> hailstoneSequence(final int n) { 
    final List<Integer> list = new ArrayList<Integer>();

記錄你的設想

在文檔中寫下變量的類型記錄了一個關於它的設想, 例如這個變量總是指向一個整型. 在編譯的時候 Java 就會檢查這個設想, 並且保證在你的代碼中沒有任何一處違背這個設想。

而使用 final 關鍵字去定義一個變量也是一種記錄設想, 要求這個變量在其被賦值之后就永遠不會再被修改, Java 也會對其進行靜態地檢查。

不幸的是 Java 並不會自動檢查所有設想,例如:n 必須為正數。

為什么我們需要寫下我們的設想呢? 因為編程就是不斷的設想, 如果我們不寫下他們, 就可能會遺忘掉他們, 而且如果以后別人想要閱讀或者修改我們的軟件, 他們就會很難理解代碼, 不得不去猜測(譯者注: 變量的含義/函數的描述/返回值描述等等)

所以在編程的時候我們必須朝着如下兩個目標努力:

  • 與計算機交流. 首先說服編譯器你的程序語法正確並且類型正確, 然后保證邏輯正確, 這樣就可以讓它在運行的時候給我們正確的結果。
  • 與其他人交流. 盡可能使你的程序易於理解, 所以當有人想要在將來某些時候修正它, 改進它或者對其進行適配的時候, 他們可以很方便地實現自己的想法。

黑客派(Hacking) vs. 工程派(Engineering)

我們已經在本門課程中編寫了一些黑客風格的代碼, 黑客派的編程風格可以理解為“放飛自我並且樂觀的”(譯者注:貶義):

  • 缺點: 在已經編寫大量代碼以后才測試它們
  • 缺點: 將所有的細節都放在腦子里, 以為自己可以永遠記住所有的代碼, 而不是將它們編寫在代碼中
  • 缺點: 認為 BUG 都不存在或者它們都非常容易發現和被修復.

而工程派對應的做法是(譯者注:褒義):

  • 優點: 一次只寫一點點, 一邊寫一邊測試. 在將來的課程中, 我們將會探討"測試優先編程" (test-first programming)
  • 優點: 記錄代碼的設想、意圖 (document the assumptions that your code depends on)
  • 優點: 靜態代碼檢查將會保護你的代碼不淪為“愚蠢的代碼”

閱讀小練習

思考下面這個Python片段:

from math import sqrt
def funFactAbout(person):
  if sqrt(person.age) == int(sqrt(person.age)):
    print("The age of " + person.name + " is a perfect square: " + str(person.age))

這塊代碼做了哪些設想(能夠正常運行不報錯)?

  • [x] person 一定有 agename 這兩個實例化變量

  • [x] person 不是 None

  • [x] person.age 不是負數

  • [x] person.age 是整數

  • [x] person.name 一定是字符串

如果這串代碼用Java來寫,下面的哪些設想可以用類型聲明來限制,或者能夠被編譯器靜態檢查出來?

  • [x] person 一定有 agename 這兩個實例化變量

  • [ ] person 不是 None

  • [ ] person.age 不是負數

  • [x] person.age 是整數

  • [x] person.name 一定是字符串


本門課程(6.031)的目標

本門課程的主要目標為學習如何生產具有如下屬性的軟件:

  • 遠離bug. 正確性 (現在看起來是正確的), 防御性 (將來也是正確的)
  • 易讀性. 我們不得不和以后有可能需要理解和修改代碼的程序員進行交流 (修改 BUG 或者添加新的功能), 那個將來的程序員或許會是幾個月或者幾年以后的你, 如果你不進行交流, 那么到了那個時候, 你將會驚訝於你居然忘記了這么多, 並且這將會極大地幫助未來的你有一個良好的設計。
  • 可改動性. 軟件總是在更新迭代的, 一些好的設計可以讓這個過程變得非常容易, 但是也有一些設計將會需要讓開發者扔掉或者重構大量的代碼。

譯者注:

  • safe from bugs (SFB),
  • easy to understand (ETU),
  • ready for change (RFC).

當然也有其他的非常重要的關於軟件的性質(例如: 性能, 實用性以及安全性), 並且他們可能會和上述三種性質是互相矛盾的。但是在 6.031 這門課程中, 我們重點關注上述三個重要性質, 並且軟件開發者一般情況下會將這三種性質列於軟件開發過程中的最重要的性質。

在這門課的學習中, 思考每一種編程語言的特性, 每一次編程練習, 每一個設計模式是非常值得的, 試着理解它們如何和上述三種特性關聯起來。

閱讀小練習

上文中那些知識點能夠幫助你遠離bug SFB ?

  • [x] 動態檢查

  • [x] 常量(final)

  • [ ] 整型溢出

  • [x] 靜態類型

上文中那些知識點能夠幫助你的代碼易於理解和閱讀 ETU ?

  • [x] documented assumptions in comments

  • [x] 常量(final)

  • [ ] 整型溢出

  • [ ] 靜態類型

上文中那些知識點能夠幫助你的代碼能夠被方便的更改 RFC ?

  • [x] documented assumptions in comments

  • [ ] 長度固定的數組

  • [x] 方法(methods)


我們為什么選擇Java作為本課程的編程語言

如果你已經學過了課程 6.009, 我們假設你已經對 Python 語言輕車熟路了, 那么為什么在這門課程中, 我們沒有使用 Python 語言而是使用了 Java 呢?

安全性是首要原因, Java 有靜態檢查機制 (主要檢查變量類型, 同時也會檢查函數的返回值和函數定義時的返回值類型是否匹配). 我們在這門課中學習軟件開發, 而在軟件開發中一個主要原則就是遠離 BUG, Java 撥號安全性達到了 11 (Java dials safety up to 11), 這讓 Java 成為一個非常好的用來進行軟件工程實踐的語言. 當然, 在其他的動態語言中也可以寫出安全的代碼, 例如 Python, 但是如果你學習過如何在一個安全的, 具有靜態代碼檢查機制的語言, 你就可以更加容易地理解這一過程。

普遍性是另一個原因, Java 在科學研究/教育/工業界廣泛被使用. Java 可以在許多平台運行, 不僅僅是 Windows/Mac/Linux. Java 也可以用來進行 web 開發 (不僅可以在服務端使用, 也可以在客戶端使用), 而且原生安卓系統也是基於 Java 開發. 盡管其他的編程語言也有更加適合用來進行編程教學 (Scheme 和 ML 浮現在腦海中), 但是令人是讓的是這些語言並沒有在現實世界中被廣泛使用. 你的簡歷上的 Java 經驗將會被認為是一個非常有利的技能. 但是注意請不要理解錯了, 你從本門課程中學到的並不是僅限定於 Java 語言, 這些知識是可以被套用在任何編程語言中的. 本門課程中最重要內容: 安全性, 清晰性, 抽象, 工程化的本能, 這些知識將會讓你游刃有余地應對各種編程語言的新潮流。

任何情況下, 一個好的程序員必須能熟練使用多種編程語言, 編程語言是一種工具, 而你必須使用正確的工具來完成你的工作. 在你完成你在 MIT 的學習生涯之前你肯定會學到其他的編程語言技能 (例如: JavaScript, C/C++, Scheme, Ruby, ML 或者 Haskell) 所以我們正在通過學習第二門語言來入門。

作為普遍性的一個結果, Java 有大量的有趣而且有用的 (包括 Java 本身自帶的庫, 以及在網絡上的庫), 也有非常多的免費並且完美的工具 (IDE 例如 Eclipse; 編輯器, 編譯器, 測試框架, 性能分析工具, 代碼覆蓋率檢測工具, 代碼風格檢查工具). 即使是 Python , 它的生態系統也沒有 Java 的更加豐富。

后悔使用 Java 的幾個原因.

  • 它很啰嗦, 這個特性使得在黑板上寫出代碼樣例是非常麻煩的.
  • 它很臃腫, 在這些年中已經積累了非常多的不同的功能和特性.
  • 它存在一些內部矛盾, 例如: final 關鍵字在不同的上下文中會有不同的含義, static 關鍵字在 Java 中和靜態代碼檢查並沒有任何關系
  • 它受到C / C ++等老式語言的影響, 原始數據類型和 switch 語句就是很好的例子
  • 它並沒有一個像 Python 一樣的解釋器, 可以允許你在解釋器中編寫一些短小的測試代碼來學習這門語言

但是總體來說, Java 對現在來說還是一款比較適合用來學習如何編寫安全的, 易於理解的, 對改變友好的代碼的編程語言, 以上就是我們課程的目標;)


總結

我們今天主要介紹的思想為靜態代碼檢查, 下面是該思想如何和我們的目標進行關聯

  • 遠離bug. 靜態代碼檢查可以通過捕捉類型錯誤等其他BUG幫助我們在運行代碼之前就發現它們
  • 易讀性. 它可以幫助我們理解, 因為所有的類型在代碼中被明確定義 (譯者注: 相比於 Python/PHP 這類動態變量類型的語言)
  • 可改動性. 靜態代碼檢查可以在你在修改你的代碼的時候定位出也需要被修改的地方, 例如: 當你改變一個變量的類型或者名稱的時候, 編譯器立即就會在所有使用到這個變量的地方顯示錯誤, 提示你也需要更新它們。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM