1. 程式人生 > >MIT 6.031 Software Construction 學習筆記:(四) Avoiding Debugging

MIT 6.031 Software Construction 學習筆記:(四) Avoiding Debugging

這章Reading 9: Avoiding Debugging給了我很大的收穫,以前一些 ACMer 的不好的程式碼習慣(當然是為了快速coding)都在這裡暴露無遺

First Defense: Make Bugs Impossible

主要是前面講的一些內容的結合,一方面是編譯器做足工作,另外一方面則是人要做足工作

  • dynamic checking 這裡主要是編譯器的工作了,e.g. java 的Array overflow, 就比c/c++ 的不檢查優秀不少
  • Immutability
  • immutable references: 加final
  • unreassignable references

Second Defense: Localize Bugs

顧名思義就是將bug放在一個很小的範圍中,方便定位

defensive programming: Checking preconditions is an example of defensive programming

就是可以在方法入口處檢查方法的前置條件

e.g

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

通常僅在前置條件檢查開銷不大的情況用這種方法,一種簡單的想法是: 例如二分在有序陣列查詢值,那麼如果檢查前置條件,顯然開銷大於整個方法的開銷,那麼久無用了,通常要保證檢查的開銷是 O(1)O(1) 的。而且注意這種檢查通常都會丟擲一個 unchecked IllegalArgumentException

Assertion

注意 java assertion 預設是關閉的,你需要開啟

What to Assert

Method argument requirements:

Method return value requirements

簡答來說就是方法的 入口和進口應該 assertion

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

注意assertion, 也不能到處都來,應為這個也是需要開銷的

What Not to Assert

  1. Many assertion mechanisms are designed so that assertions are executed only during testing and debugging, and turned off when the program is released to users. 由於這個原因,不要做帶有程式副作用(side-effect) 的assertion, 即你的程式執行結果應該與assertion 無關
// don't do this:
assert list.remove(x);

同樣由於這個原因在重要的地方應該講assertion換成 exception 丟擲,例如 條件或者switch語言沒有覆蓋完所有情形的時候

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);
}
  1. 不要做程式之外的測試
// don't do this:
x = y + 1;
assert x == y+1;

這不是在測試程式碼,而是在測試JVM和編譯器

Incremental Development

寫一段程式碼,(e.g. 完成一個小模組,完成一個小功能) 就測試一下,而不是寫一大堆全部完了再測試

這在測試那一張講過:

  • unit test
  • regression test

Modularity & Encapsulation

模組化和封裝很有利於將bug侷限在很小的區域性

關於訪問控制 和小函式來模組化,這裡都不說了,重點說一下這個,變數作用域(variable scope)的好處

  • Always declare a loop variable in the for-loop initializer.
  • **Declare a variable only when you first need it, and in the innermost curly-brace block that you can. **
  • Avoid global variables.

Very bad idea, especially as programs get large. Global variables are often used as a shortcut to provide a parameter to several parts of your program. It’s better to just pass the parameter into the code that needs it, rather than putting it in global space where it can inadvertently be reassigned.

它說的很好,很多時候我們ACMer用全域性變數都是為了,在函式中不用傳引數,節省時間,這個程式設計技巧不可取。(這裡插播題外話,想起多次在ACM題目中由於初始化問題導致找不出bug浪費大量時間,vector…這種容器)

summary

In this reading, we looked at some ways to minimize the cost of debugging:

Avoid debugging

make bugs impossible with techniques like static typing, automatic dynamic checking, and immutable types & unreassignable references

Keep bugs confined

  1. failing fast with assertions keeps a bug’s effects from spreading
  2. incremental development and unit testing confine bugs to your recent code
  3. scope minimization reduces the amount of the program you have to search