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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

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

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

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




譯者:李秋豪

審校:

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


本次課程的目標(biāo)

  • 如何避開調(diào)試(debugging)
  • 當(dāng)你不得不進(jìn)行調(diào)試時(shí),如何確保它不會(huì)太復(fù)雜


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

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

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

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

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

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

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

思考下面這個(gè)例子:

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

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

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

在下面的閱讀小練習(xí)中你會(huì)找打答案。一定要注意final的含義,它僅僅確保了索引的對(duì)象不會(huì)變?yōu)閯e的對(duì)象,而對(duì)象本身的值是可能發(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í)行下面的語句,請(qǐng)選出合法的語句:

  • [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

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

vowel0

y

vowel1

a

vowel2

uoiea

vowel3

aeiou

vowel4

zeiou

vowel5

zeiou


第二道防御:將Bug本地化

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

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

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

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

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

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

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


斷言

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

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

assert x >= 0;

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

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

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

如果x為-1,這個(gè)斷言就會(huì)失敗并打印:

x is -1

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

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

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

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

在用JUnit進(jìn)行測(cè)試時(shí)也最好將斷言打開,你可以通過以下代碼測(cè)試斷言是否打開:

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

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

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


什么時(shí)候需要斷言

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

檢查方法的返回要求,這樣的檢查也稱為“自檢查(self check)” 。例如,sqrt可能會(huì)在返回前檢查結(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)該在什么時(shí)候?qū)懮蠑嘌?#xff1f;你應(yīng)該在寫代碼的時(shí)候而非寫完之后添加斷言,因?yàn)樵趯懘a的時(shí)候你的心里會(huì)有一些必須滿足的條件,這些必須滿足的條件就可以用斷言檢查,而寫完之后再添加就可能會(huì)忘掉這些必要條件。

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


什么時(shí)候不需要斷言

運(yùn)行時(shí)的斷言檢查并不是能隨意使用的,如果用的不恰當(dāng),它們會(huì)像毫無意義的注釋一樣讓代碼變得繁瑣。例如:

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

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

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

很多時(shí)候,斷言這種機(jī)制只用于程序的測(cè)試和調(diào)試階段,當(dāng)程序發(fā)行時(shí)會(huì)全部取消。Java也是這樣。正因?yàn)閿嘌钥赡軙?huì)被取消,你的代碼不能依賴于斷言檢查是否被執(zhí)行,也就是說,斷言檢查不能有副作用(side-effects),例如

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

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

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

相似的,在進(jìn)行條件語句覆蓋檢查時(shí),不要使用斷言,因?yàn)樗鼈冊(cè)谖磥砜赡軙?huì)被關(guān)閉。對(duì)于非法的情況,應(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

思考下面這個(gè)函數(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ā)方法中,你每次只完成程序的一小部分,然后對(duì)這部分進(jìn)行完全的測(cè)試,隨后再進(jìn)行下一步的小范圍開發(fā),并最終完成開發(fā)。通過這種方式,我們可以將大多數(shù)bug控制在我們剛剛修改/增加的代碼中,從而降低debug的困難。

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

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


模塊化與封裝

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

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

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

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

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

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

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

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

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

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

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

現(xiàn)在,i只能被for語句和...修改了。你不再需要考慮 doSomeThings()和程序其他位置是否會(huì)對(duì)i進(jìn)行更改,因?yàn)槠渌恢玫拇a都無法訪問這里的i 。

最小化作用域是一個(gè)將bug本地化的有力工具。對(duì)于Java來說,這里有一些好用的點(diǎn)子:

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

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

    應(yīng)該這樣寫:

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

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

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

  • 避免使用全局變量. 這是一個(gè)很糟糕的注意,尤其是當(dāng)程序變大的時(shí)候。通常來說,全局變量是為了方便向幾個(gè)方法傳入同樣的參數(shù),但是這樣不如分別向各個(gè)方法傳入,因?yàn)槿肿兞亢芸赡軙?huì)被不經(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

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

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()里面就停住。上圖畫出了此刻程序不完整的快照?qǐng)D。試著填上每一個(gè)標(biāo)簽內(nèi)的內(nèi)容。如果你你忘了每一個(gè)方框代表的含義,參考:“代碼評(píng)審_在快照?qǐng)D中的各種變量”

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

  • [ ] address

  • [ ] roommates

  • [ ] bathrooms (instance variable)

  • [ ] MAXIMUM_OCCUPANCY_PER_BATHROOM

  • [ ] newAddress

  • [ ] bathrooms (local variable)

  • [x] newRoommate

  • [ ] args

  • [ ] apt

  • [x] this

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

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

  • [ ] address

  • [ ] roommates

  • [ ] bathrooms (instance variable)

  • [ ] MAXIMUM_OCCUPANCY_PER_BATHROOM

  • [ ] newAddress

  • [ ] bathrooms (local variable)

  • [ ] newRoommate

  • [x] args

  • [x] apt

  • [ ] this

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

  • [x] address

  • [x] roommates

  • [x] bathrooms (instance variable)

  • [ ] MAXIMUM_OCCUPANCY_PER_BATHROOM

  • [ ] newAddress

  • [ ] bathrooms (local variable)

  • [ ] newRoommate

  • [ ] args

  • [ ] apt

  • [ ] this

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

  • [ ] address

  • [ ] roommates

  • [ ] bathrooms (instance variable)

  • [x] MAXIMUM_OCCUPANCY_PER_BATHROOM

  • [ ] newAddress

  • [ ] bathrooms (local variable)

  • [ ] newRoommate

  • [ ] args

  • [ ] apt

  • [ ] this

此刻快照?qǐng)D中不存在哪些變量?

  • [ ] 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)試代價(jià)的方法:

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

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

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

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

總結(jié)

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

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