日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

麻省理工18年春软件构造课程阅读09“避免调试”

發(fā)布時間:2023/12/10 编程问答 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 麻省理工18年春软件构造课程阅读09“避免调试” 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

本文內(nèi)容來自MIT_6.031_sp18: Software Construction課程的Readings部分,采用CC BY-SA 4.0協(xié)議。

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




譯者:李秋豪

審校:

V1.0 Sun Mar 25 13:32:29 CST 2018


本次課程的目標

  • 如何避開調(diào)試(debugging)
  • 當你不得不進行調(diào)試時,如何確保它不會太復(fù)雜


第一道防御:讓Bug無法產(chǎn)生

最好的防御策略就是在設(shè)計上讓Bug無法產(chǎn)生。

我們之前已經(jīng)談到過靜態(tài)檢查 。靜態(tài)檢查能夠在編譯期發(fā)現(xiàn)很多bug。

我們也看到了一些動態(tài)檢查的例子。例如,Java會通過動態(tài)檢查讓數(shù)組越界訪問的bug不可能存在。如果你試著越界訪問一個數(shù)組或列表,Java就會在運行時報錯。在一些更老的語言中,例如C和C++,這樣的訪問是允許的——可能會導(dǎo)致bug和 安全漏洞.

不可變性也是另一種防止bug的設(shè)計策略。在創(chuàng)建時,一個不可變類型的對象的值就確定了,接下來可以保證不會發(fā)生改變。

字符串是一種不可變類型。你無法通過String內(nèi)置的方法更改它內(nèi)部存儲的字符。因此,字符串可以被安全地傳入/分享給程序的各個地方。

Java也提供了不變的索引:如果一個變量聲明時用final修飾,那么它的索引一旦確定就不能更改了。在實踐中,你應(yīng)該盡可能對方法、參數(shù)、本地變量使用final。正如變量的類型一樣,final也是一種良好的文檔,它告訴了讀者這個變量索引的對象不會變?yōu)閯e的對象,而且這種檢查也是靜態(tài)的,由編譯器負責(zé)。

思考下面這個例子:

final char[] vowels = new char[] { 'a', 'e', 'i', 'o', 'u' };

vowels 變量被聲明為final ,但是它指向的對象真的不會發(fā)生改變嗎?以下哪一個語句是不合法的(會被編譯器捕捉),哪一句又是合法的?

vowels = new char[] { 'x', 'y', 'z' }; vowels[0] = 'z';

在下面的閱讀小練習(xí)中你會找打答案。一定要注意final的含義,它僅僅確保了索引的對象不會變?yōu)閯e的對象,而對象本身的值是可能發(fā)生更改的。

閱讀小練習(xí)

Final references, immutable objects

思考下面的代碼,它們按順序執(zhí)行:

char vowel0 = 'a'; final char vowel1 = vowel0;String vowel2 = vowel1 + "eiou"; final String vowel3 = vowel2;char[] vowel4 = new char[] { vowel0, 'e', 'i', 'o', 'u' }; final char[] vowel5 = vowel4;

在上面的語句執(zhí)行完后,再按順序執(zhí)行下面的語句,請選出合法的語句:

  • [x] vowel0 = 'y';

  • [ ] vowel1 = vowel0;

  • [x] vowel2 = "uoie" + vowel1;

  • [ ] vowel3 = vowel2;

  • [ ] vowel2[0] = 'x';

  • [ ] vowel3[0] = 'x';

  • [x] vowel4 = vowel5;

  • [ ] vowel5 = vowel4;

  • [x] vowel4[0] = 'x';

  • [x] vowel5[0] = 'z';

Afterwards

當上一個練習(xí)的合法語句全部執(zhí)行完以后,各個變量的值分別是多少?

vowel0

y

vowel1

a

vowel2

uoiea

vowel3

aeiou

vowel4

zeiou

vowel5

zeiou


第二道防御:將Bug本地化

如果我們不能阻止bug產(chǎn)生,那么應(yīng)該盡可能將它們的觸發(fā)地點集中在一小塊地方,這樣以后找bug的時候會方便許多。當bug被本地化在一個小方法或模塊的時候,我們可能只需要閱讀代碼就能發(fā)現(xiàn)bug。

我們之前已經(jīng)討論過了快速失敗/報錯 :問題暴露的越早(或者離產(chǎn)生的地方越近),修復(fù)bug就會越容易。

現(xiàn)在看一個簡單的例子:

/*** @param x requires x >= 0* @return approximation to square root of x*/ public double sqrt(double x) { ... }

假設(shè)有一個人用負數(shù)去調(diào)用了sqrt .sqrt最合理的行為應(yīng)該是什么?既然調(diào)用者沒有滿足前置條件,講道理方法可以做任何事情:返回一個任意值、進入死循環(huán)、融化CPU等等。然而,我們應(yīng)該盡早報告這個調(diào)用者的bug。例如,我們可以對這個前置條件做一個檢查,如果不滿足則拋出一個非檢查異常IllegalArgumentException :

/*** @param x requires x >= 0* @return approximation to square root of x*/ public double sqrt(double x) { if (! (x >= 0)) throw new IllegalArgumentException();... }

檢查前置條件是防御性編程的一個例子 。程序往往都會有bug,而防御性編程減輕了bug的影響(即使你不知道bug在哪)。


斷言

在實踐中我們經(jīng)常需要定義一套程式來進行這樣的防御性檢查,它們通常被稱為asser() (斷言)。

在Java中,assert是一種語句而非方法。最簡單的斷言語句會接受一個布爾表達式,如果這個表達式的值為假則拋出一個 AssertionError 。

assert x >= 0;

斷言也是一種很好的文檔,它強制規(guī)定了特定時候程序應(yīng)有的狀態(tài),例如 assert x >= 0 就是在說“在這行代碼執(zhí)行時,x不能是負數(shù)”。不過和注釋文檔不同,斷言是可執(zhí)行的,它會在運行的時候進行檢查。

Java的段嚴重也可以包含一個描述語句,通常是字符串,也可以原始數(shù)據(jù)類型或者對象索引。在斷言失敗時,描述性的消息會打印出來,因此程序員可以根據(jù)描述語句進行跟蹤調(diào)試。描述語句跟在布爾表達式后面,用冒號隔開,例如:

assert x >= 0 : "x is " + x;

如果x為-1,這個斷言就會失敗并打印:

x is -1

以及此時的棧幀情況(告訴你斷言的位置和函數(shù)調(diào)用情況)。這些信息通常以及足夠用來排除bug了。

一個嚴重的問題是,Java默認關(guān)閉斷言。。

如果你在Java默認的環(huán)境下運行程序,你所有的斷言都不會被檢查!Java的設(shè)計者這么做是因為斷言檢查會帶來性能上的損失。例如,我們寫了一個二分查找方法,而該方法的前置條件是數(shù)組已經(jīng)排序。所以我們的斷言檢查應(yīng)該是一個線性的復(fù)雜度,這樣就會改變整個方法的復(fù)雜度。但是,對于測試來說,這樣的檢查是必須的,因為斷言檢查會讓你的調(diào)試更加簡單。當程序發(fā)布時,這些測試斷言就會被去除掉。另外,對于大多數(shù)應(yīng)用來說,斷言檢查的性能損失和后續(xù)的代碼比起來不算什么,所以它們還是值得的。

為了顯式的打開斷言,你需要在使用Java虛擬機的時候加上 -ea 參數(shù)。在Eclipse中,你需要進入 Run → Run Configurations → Arguments,然后在VM參數(shù)中添加 -ea 。如果想要將 -ea 設(shè)為默認參數(shù),進入 Preferences → Java → Installed JREs → Edit → Default VM Arguments,然后加上 -ea 。這些在 Getting Started 中有詳細描述。

在用JUnit進行測試時也最好將斷言打開,你可以通過以下代碼測試斷言是否打開:

@Test(expected=AssertionError.class) public void testAssertionsEnabled() {assert false; }

如果斷言打卡, assert false 語句就會拋出一個 AssertionError。而測試前的(expected=AssertionError.class) 表示這個測試應(yīng)該拋出AssertionError,所以測試會通過。如果斷言關(guān)閉,那么就不會有AssertionError拋出,測試也不會通過。

注意到Java中的 asser語句并不等同于JUnit中的 assertTrue(), assertEquals()這些方法。雖然它們都是對代碼狀態(tài)進行預(yù)測,但是使用的上下文不一樣。 asser語句是在實現(xiàn)的代碼中使用的,以此來進行防御性編程。而Junit的 assert...() 方法是放在JUnit的測試文件中的。如果沒有使用-ea參數(shù)開啟斷言, assert 是不會檢查的,但是JUnit的斷言方法還是會運行。


什么時候需要斷言

檢查方法的參數(shù)要求,例如上面的 sqrt例子。

檢查方法的返回要求,這樣的檢查也稱為“自檢查(self check)” 。例如,sqrt可能會在返回前檢查結(jié)果是否在誤差范圍內(nèi):

public double sqrt(double x) {assert x >= 0;double r;... // compute result rassert Math.abs(r*r - x) < .0001;return r; }

應(yīng)該在什么時候?qū)懮蠑嘌?#xff1f;你應(yīng)該在寫代碼的時候而非寫完之后添加斷言,因為在寫代碼的時候你的心里會有一些必須滿足的條件,這些必須滿足的條件就可以用斷言檢查,而寫完之后再添加就可能會忘掉這些必要條件。

譯者注:這個地方我有些疑惑,對于前置條件的檢查,到底應(yīng)該拋出非檢查異常還是使用斷言呢?有幾點可以肯定:斷言是對于開發(fā)過程中的設(shè)計而言的,意在表示設(shè)計上不能達到的狀態(tài),是面向開發(fā)者的,在后期可以取消。而非檢查異常似乎是對于使用者來說的,即強制要求前置條件得到滿足。這里引用一篇stackExchange上的回答:

Assertions are removed at runtime unless you explicitly specify to "enable assertions" when compiling your code. Java Assertions are not to be used on production code and should be restricted to private methods (see Exception vs Assertion), since private methods are expected to be known and used only by the developers. Also assert will throw AssertionError which extends Error not Exception, and which normally indicates you have a very abnormal error (like "OutOfMemoryError" which is hard to recover from, isn't it?) you are not expected to be able to treat.

Remove the "enable assertions" flag, and check with a debugger and you'll see that you will not step on the IllegalArgumentException throw call... since this code has not been compiled (again, when "ea" is removed)

It is better to use the second construction for public/protected methods, and if you want something that is done in one line of code, there is at least one way that I know of. I personally use the Spring Framework's Assert class that has a few methods for checking arguments and that throw "IllegalArgumentException" on failure. Basically, what you do is:

Assert.notNull(obj, "object was null");

... Which will in fact execute exactly the same code you wrote in your second example. There are a few other useful methods such as hasText, hasLength in there.


什么時候不需要斷言

運行時的斷言檢查并不是能隨意使用的,如果用的不恰當,它們會像毫無意義的注釋一樣讓代碼變得繁瑣。例如:

// don't do this: x = y + 1; assert x == y+1;

這個代碼并不能發(fā)現(xiàn)你代碼中的bug,事實上,它只能發(fā)現(xiàn)編譯器或者虛擬機的問題——而這幾乎是不可能出問題的。如果一個斷言檢查在上下文中是無意義的,刪除它。

永遠不要用斷言檢查程序之外的條件,例如文件是否存在、網(wǎng)絡(luò)是否可到達、或者用戶的輸入是否正確。斷言應(yīng)該用來保證程序內(nèi)部的合理性而非外部。當斷言失敗時,它意味著程序已經(jīng)進入了一個設(shè)計上錯誤的狀態(tài)(bug),而外部的條件是你無法通過更改代碼能預(yù)測的,所以它們不是bug。通常來說,這些外部條件應(yīng)該使用已檢查異常進行報告。

很多時候,斷言這種機制只用于程序的測試和調(diào)試階段,當程序發(fā)行時會全部取消。Java也是這樣。正因為斷言可能會被取消,你的代碼不能依賴于斷言檢查是否被執(zhí)行,也就是說,斷言檢查不能有副作用(side-effects),例如

// don't do this: assert list.remove(x);

如果斷言檢查被關(guān)閉,那么這個語句就不會被執(zhí)行,而 x 也就不會從列表中刪除了。應(yīng)該這樣寫:

boolean found = list.remove(x); assert found;

相似的,在進行條件語句覆蓋檢查時,不要使用斷言,因為它們在未來可能會被關(guān)閉。對于非法的情況,應(yīng)該拋出異常:

switch (vowel) {case 'a':case 'e':case 'i':case 'o':case 'u': return "A";default: throw new AssertionError("must be a vowel, but was: " + vowel);/* The exception in the default clause has the effect of asserting that vowel must be one of the five vowel letters.*/ }

閱讀小練習(xí)

Assertions

思考下面這個函數(shù):

/*** Solves quadratic equation ax^2 + bx + c = 0.* * @param a quadratic coefficient, requires a != 0* @param b linear coefficient* @param c constant term* @return a list of the real roots of the equation*/ public static List<Double> quadraticRoots(final int a, final int b, final int c) {List<Double> roots = new ArrayList<Double>();// A... // compute roots // Breturn roots; }

在A處應(yīng)該寫上哪一條語句?

  • [x] assert a != 0;

  • [ ] assert b != 0;

  • [ ] assert c != 0;

  • [ ] assert roots.size() >= 0;

  • [ ] assert roots.size() <= 2;

  • [ ] for (double x : roots) { assert Math.abs(a*x*x + b*x + c) < 0.0001; }

在B處寫上哪一條語句是合理的?

  • [ ] assert a != 0;

  • [ ] assert b != 0;

  • [ ] assert c != 0;

  • [ ] assert roots.size() >= 0;

  • [x] assert roots.size() <= 2;

  • [x] for (double x : roots) { assert Math.abs(a*x*x + b*x + c) < 0.0001; }


增量式開發(fā)

譯者注:Incremental development 也可譯為“漸增性開發(fā)”

增量式開發(fā)是一種將bug控制在小范圍內(nèi)的好方法。在這種開發(fā)方法中,你每次只完成程序的一小部分,然后對這部分進行完全的測試,隨后再進行下一步的小范圍開發(fā),并最終完成開發(fā)。通過這種方式,我們可以將大多數(shù)bug控制在我們剛剛修改/增加的代碼中,從而降低debug的困難。

在我們之前的閱讀中(譯者注:“測試”),談到了兩個可以在增量式開發(fā)中幫助我們的測試方法:

  • 單元測試:每次只對一個獨立的模塊進行測試,這樣可以將bug的范圍控制在模塊中——或者在測試用例本身中。
  • 回歸測試:當你在系統(tǒng)中添加新的功能或修改一個bug后,重新運行所有測試,防止代碼“回退”。


模塊化與封裝

你也可以通過好的設(shè)計將bug本地化。

模塊化.模塊化意味著將你的程序分成幾個模塊,每一個模塊都是單獨設(shè)計、實現(xiàn)、測試,并且可以在別的地方進行復(fù)用。模塊化的反面是使用一個“大塊”系統(tǒng)——其中的每一行的正確執(zhí)行都依賴著前面的代碼。

例如,如果一個程序只有一個龐大的main函數(shù),那他就是非模塊化的,這樣的代碼會很難懂,也很難將bug孤立出來。與此相對,如果一個程序被分為幾個小的函數(shù)和類,那它就是偏模塊化的。

封裝.封裝意味著你在模塊周圍建立起一道圍墻(或者說一個殼或膠囊),以此讓模塊只對自己內(nèi)部的代碼行為負責(zé),其他模塊的錯誤行為也不會影響到它的正確性。

一種封裝的方法就是使用 訪問控制,大多數(shù)時候就是使用 public 和 private 來控制變量和方法的可見/可訪問范圍。一個公共的方法和變量可以被任何地方的代碼訪問(假設(shè)它們所處的類也是公共的)。而一個私有的方法或變量只能被相同類的代碼訪問。盡可能使用private而非public ,特別是對于變量而言。通過控制訪問范圍,我們能縮小bug產(chǎn)生的范圍和debug時的搜索范圍。

另一種封裝的方法就是使用變量作用域。作用域是指程序源代碼中定義這個變量的區(qū)域。簡單的說,作用域就是變量與函數(shù)的可訪問/可見范圍。全局變量擁有全局作用域,函數(shù)參數(shù)作用于整個函數(shù)(不包括子函數(shù)),局部變量作用于聲明語句到下一個花括號為止。盡量使用和保持局部變量的作用范圍,我們就越容易定位bug,例如,下面是一個循環(huán):

for (i = 0; i < 100; ++i) {...doSomeThings();... }

但是你發(fā)現(xiàn)這個循環(huán)一直沒有停止——即i一直沒有到100.似乎某個人在某個地方更改了i的值,但是在哪呢?這有很多種可能性,例如你將i定義成了全局變量:

public static int i; ... for (i = 0; i < 100; ++i) {...doSomeThings();... }

現(xiàn)在它的作用域是整個程序,它可以被任何地方的代碼改變!例如在doSomeThings()中,在doSomeThings()的子函數(shù)中,甚至在另一個并行的線程中。但是如果我們將i聲明成一個只在循環(huán)中存在的變量:

for (int i = 0; i < 100; ++i) {...doSomeThings();... }

現(xiàn)在,i只能被for語句和...修改了。你不再需要考慮 doSomeThings()和程序其他位置是否會對i進行更改,因為其他位置的代碼都無法訪問這里的i 。

最小化作用域是一個將bug本地化的有力工具。對于Java來說,這里有一些好用的點子:

  • 永遠在for語句內(nèi)部聲明循環(huán)參量 所以羨慕這樣的寫法就是不對的,它讓for循環(huán)外部的剩余代碼也能更改i:

    int i; for (i = 0; i < 100; ++i) {

    應(yīng)該這樣寫:

    for (int i = 0; i < 100; ++i) {

    這時i只能作用于for內(nèi)部了。

  • 盡量在需要使用變量的時候才聲明它,并且盡量將它放在最內(nèi)部的花括號內(nèi). 在Java中,變量作用域是以花括號作為邊界的,所以你應(yīng)該盡可能將變量聲明放在需要該變量的最內(nèi)花括號內(nèi)。不要在方法的一開始就聲明變量——這樣會使得它們的作用域變大。另外,在一些非靜態(tài)語言中,例如Python和JavaScript,變量的作用域通常是整個方法,所以你不能將作用域控制在某一個范圍。

  • 避免使用全局變量. 這是一個很糟糕的注意,尤其是當程序變大的時候。通常來說,全局變量是為了方便向幾個方法傳入同樣的參數(shù),但是這樣不如分別向各個方法傳入,因為全局變量很可能會被不經(jīng)意的修改掉。

閱讀小練習(xí)

Variable scope

思考下面的代碼(沒有寫出一些變量的聲明):

1 class Apartment { 2 Apartment(String newAddress, int bathrooms) { 3 this.address = newAddress; 4 this.roommates = new HashSet<Person>(); 5 this.bathrooms = bathrooms; 6 } 7 8 String getAddress() { 9 return address; 10 } 11 12 void addRoommate(Person newRoommate) { 13 roommates.add(newRoommate); 14 if (roommates.size() > MAXIMUM_OCCUPANCY_PER_BATHROOM * bathrooms) { 15 roommates.remove(newRoommate); 16 throw new TooManyPeopleException(); 17 } 18 } 19 20 int getMaximumOccupancy() { 21 return MAXIMUM_OCCUPANCY_PER_BATHROOM * bathrooms; 22 } 23 }

以下哪一行處于newRoommate 的作用域?

  • [ ] line 3

  • [ ] line 8

  • [x] line 13

  • [x] line 16

  • [ ] line 20

以下哪一行處于 address (沒有寫出聲明)的作用域?

  • [x] lines 2-22

  • [ ] lines 3-5

  • [ ] line 9

  • [ ] lines 13-17

以下哪一條 roommates 的聲明是最合理的?

  • [ ] List<Person> roommates;

  • [ ] Set<Person> roommates;

  • [x] final Set<Person> roommates;

  • [ ] HashSet<Person> roommates;

以下哪一條 MAXIMUM_OCCUPANCY_PER_BATHROOM 的聲明是最合理的?

  • [ ] int MAXIMUM_OCCUPANCY_PER_BATHROOM = 5;

  • [ ] final int MAXIMUM_OCCUPANCY_PER_BATHROOM = 5;

  • [ ] static int MAXIMUM_OCCUPANCY_PER_BATHROOM = 5;

  • [x] static final int MAXIMUM_OCCUPANCY_PER_BATHROOM = 5;

Snapshots of scope

下面是上一題的代碼,不過將代碼補全了:

class Apartment {final String address;final Set<Person> roommates;final int bathrooms;static final MAXIMUM_OCCUPANCY_PER_BATHROOM = 5;Apartment(String newAddress, int bathrooms) {this.address = newAddress;this.roommates = new HashSet<Person>();this.bathrooms = bathrooms;}String getAddress() {return address;}void addRoommate(Person newRoommate) {roommates.add(newRoommate);if (roommates.size() > MAXIMUM_OCCUPANCY_PER_BATHROOM * bathrooms) {roommates.remove(newRoommate);throw new TooManyPeopleException();}}int getMaximumOccupancy() {return MAXIMUM_OCCUPANCY_PER_BATHROOM * bathrooms;}public static void main(String[] args) {Apartment apt = new Apartment("221 Baker St", 1);apt.addRoommate(new Person("Sherlock Holmes"));} }

假設(shè)我們將代碼執(zhí)行到 addRoommate()里面就停住。上圖畫出了此刻程序不完整的快照圖。試著填上每一個標簽內(nèi)的內(nèi)容。如果你你忘了每一個方框代表的含義,參考:“代碼評審_在快照圖中的各種變量”

在A標簽處應(yīng)該有哪些變量?

  • [ ] address

  • [ ] roommates

  • [ ] bathrooms (instance variable)

  • [ ] MAXIMUM_OCCUPANCY_PER_BATHROOM

  • [ ] newAddress

  • [ ] bathrooms (local variable)

  • [x] newRoommate

  • [ ] args

  • [ ] apt

  • [x] this

this作為隱式參數(shù)傳入方法

在B標簽處應(yīng)該有哪些變量?

  • [ ] address

  • [ ] roommates

  • [ ] bathrooms (instance variable)

  • [ ] MAXIMUM_OCCUPANCY_PER_BATHROOM

  • [ ] newAddress

  • [ ] bathrooms (local variable)

  • [ ] newRoommate

  • [x] args

  • [x] apt

  • [ ] this

在C標簽處應(yīng)該有哪些變量?

  • [x] address

  • [x] roommates

  • [x] bathrooms (instance variable)

  • [ ] MAXIMUM_OCCUPANCY_PER_BATHROOM

  • [ ] newAddress

  • [ ] bathrooms (local variable)

  • [ ] newRoommate

  • [ ] args

  • [ ] apt

  • [ ] this

在D標簽處應(yīng)該有哪些變量?

  • [ ] address

  • [ ] roommates

  • [ ] bathrooms (instance variable)

  • [x] MAXIMUM_OCCUPANCY_PER_BATHROOM

  • [ ] newAddress

  • [ ] bathrooms (local variable)

  • [ ] newRoommate

  • [ ] args

  • [ ] apt

  • [ ] this

此刻快照圖中不存在哪些變量?

  • [ ] address

  • [ ] roommates

  • [ ] bathrooms (instance variable)

  • [ ] MAXIMUM_OCCUPANCY_PER_BATHROOM

  • [x] newAddress

  • [x] bathrooms (local variable)

  • [ ] newRoommate

  • [ ] args

  • [ ] apt

  • [ ] this

此刻哪些變量是在 addRoommate()中不可訪問的(但是存在)?

  • [ ] address
  • [ ] roommates
  • [ ] bathrooms (instance variable)
  • [ ] MAXIMUM_OCCUPANCY_PER_BATHROOM
  • [ ] newAddress
  • [ ] bathrooms (local variable)
  • [ ] newRoommate
  • [x] args
  • [x] apt
  • [ ] this


總結(jié)

在這篇閱讀中,我們介紹了幾種最小化調(diào)試代價的方法:

  • 避免調(diào)試
    • 使用靜態(tài)類型檢查、動態(tài)檢查、不可變類型和不可變索引讓bug無法產(chǎn)生。
  • 限制bug范圍
    • 通過斷言檢查、快速失敗讓bug的影響不擴散。
    • 通過增量式開發(fā)和單元測試讓bug盡量只存在于剛剛修改的代碼中。
    • 最小化變量作用域使得搜尋范圍減小。

最后還是將這次閱讀的內(nèi)容和我們的三個目標聯(lián)系起來:

  • 遠離bug. 本閱讀的內(nèi)容就是如何避免和限制bug。
  • 易于理解. 靜態(tài)類型檢查、final以及斷言都是額外的“注釋”——它們體現(xiàn)了你對程序狀態(tài)的假設(shè)。而縮小作用域使得讀者可以更好的理解變量是如何使用的,因為他們需要瀏覽的代碼范圍變小了。
  • 可改動. 斷言檢查和靜態(tài)檢查都是能夠自動檢查的“假設(shè)”,所以如果未來有一個程序員錯誤改動了代碼,那么違背假設(shè)的錯誤就能馬上檢測到。

轉(zhuǎn)載于:https://www.cnblogs.com/liqiuhao/p/8644241.html

總結(jié)

以上是生活随笔為你收集整理的麻省理工18年春软件构造课程阅读09“避免调试”的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。