---
# System prepended metadata

title: Bug是會傳染的

---

# Bug是會傳染的
**Bug發現的越晚，修起來越痛苦**
1. 會寫出與bug有關的程式碼
2. 成為下游程式碼必須依賴的結果

### 不要指望你的使用者
* WHY? 
有時候他們會回報一些問題，**但其實更多時候他們會在心裡假設，他們所看到的行為就是你要提供給他們的行為**
* HOW?
寫出更符合使用者取向的文件（軟體工程師與PM的溝通)
* BUT
實際使用者無法想我們一樣，明確的知道功能的用途，所以會把Bug當成功能的一部分
* WHAT?
使用自動化測試：TDD

### 自動化測試可能還蠻棘手的
有些東西很難進行測試：如有損型音頻壓縮編碼解碼器
其他例子：[有狀態服務(Stateful Service)](https://medium.com/brobridge/%E5%A6%82%E4%BD%95%E8%A7%A3%E8%80%A6%E7%85%A9%E4%BA%BA%E7%9A%84%E6%9C%89%E7%8B%80%E6%85%8B%E6%9C%8D%E5%8B%99-stateful-service-bfc14f865878)

### 無狀態的程式碼，更容易進行測試
* 程式碼內盡量不要用到"狀態"之類的東西 - 純粹只依賴"輸入"，完全沒有"副作用"，並且輸出結果完全可以預測（rails內的callback就是種副作用 - 被視為一種anti-pattern)

依賴外部狀態的簡單例子
```ruby=
# Good code
def add_numbers(a, b)
  a + b
end

puts add_numbers(2, 3)  # 输出 5
puts add_numbers(5, 5)  # 输出 10

```

```ruby=
# Bad code
@counter = 0  # 外部状态

def increment(input)
  @counter += input  # 修改了外部状态
  @counter           # 返回了新的状态值
end

puts increment(5)  # 输出 5
puts increment(5)  # 输出 10，与第一次调用结果不同

```
Ｑ：在rails service內，init之後的call/perform執行時，應該是投入參數比較好，還是用實體變數比較好？

```ruby=
class Service
    def initialize(params1, params2)
        @params1 = params1
        @params2 = params2
    end
    
    def call
        result = method1(@params1, @params2)
        result = method2(result)
        
        result
    end
    
    private
    
    def method1(params1, params2)
    end
    
    def method2(result)
    end
end
```

```ruby=
class Service
    def initialize(params1, params2)
        @params1 = params1
        @params2 = params2
    end
    
    def call
        @result = method1(@params1, @params2)
        method2
        
        @result
    end
    
    private
    
    def method1(params1, params2)
    end
    
    def method2
        @result
    end
end
```

* 如果想對程式碼進行徹底而全面的測試，你就必須向他完整展示所有可能遇到的各種狀態，然後再針對不同的狀態，對相應的輸出逐一進行評估。如果是純函示，"函式的參數"就是唯一重要的狀態。但如果你引入了副作用/內部狀態/或向外調用任何函式，狀態數量就有可能暴增。

### 如果無法消除狀態，那就對他進行審查
```ruby=
class Character
  # ...

  def self.audit_all
    @@all_characters.each_with_index do |character, index|
      if index > 0
        prev_character = @@all_characters[index - 1]
        raise "Characters are out of order" unless character.priority >= prev_character.priority
      end

      character.audit
    end
  end

  def audit
    # Implementation of individual character audit checks
    # For example:
    raise "Invalid character state" if self.priority < 0
  end

  # ...
end

```
使用內部測試來確保內部狀態有沒有保持一至

### 別太信賴前來調用的那一方
* 並非我們得程式碼有bug，而是因為前來進行調用的程式碼寫了一堆bug
* 最容易造成bug的並不是前來調用得程式碼，而是被調用的程式碼，因為"你"所處的位置，確實才是更容易發現錯誤的位置
* 程式碼給出"未定義"的結果，這本身其實就是介面設計不良的一種跡象（ex: find_by會得到nil）

# 取個好名字，本身就是最好的說明
* 名字應該要能概括出這東西的重要之處，並引導那些看到它的人，讓大家知道應該把它想成什麼樣的東西

### 不要為了少打幾個字，而去簡化名稱
* 讀程式碼的次數，遠多於寫程式碼的次數

數值演算法風格
```c=
void cp(
    int n,
    float rr[],
    float ii[],
    float xr,
    float xi,
    float *yr,
    float *yi)
{
    float rn = 1.0f, in = 0.0f;
    *yr = 0.0f;
    *yi = 0.0f;
    for (int i = 0; i <= n; ++i)
    {
        *yr += rr[i] * rn - ii[i] * in;
        *yi += ii[i] * rn + rr[i] * in;
        float rn2 = rn * xr - in * xi;
        in = in * xr + rn * xi;
        rn = rn2;
    }
}

```
給予適合的名字
```c=
void evaluateComplexPolynomial(
    int degree,
    float realCoeffs[],
    float imagCoeffs[],
    float realX,
    float imagX,
    float *realY,
    float *imagY)
{
    float realXN = 1.0f, imagXN = 0.0f;
    *realY = 0.0f;
    *imagY = 0.0f;
    for (int n = 0; n <= degree; ++n)
    {
        *realY += realCoeffs[n] * realXN - imagCoeffs[n] * imagXN;
        *imagY += imagCoeffs[n] * realXN + realCoeffs[n] * imagXN;
        float realTemp = realXN * realX - imagXN * imagX;
        imagXN = imagXN * realX + realXN * imagX;
        realXN = realTemp;
    }
}
```

### 不要混用不同的約定慣例做法
```c=
template <class ELEM, int MAX_COUNT = 8>
class FixedVector
{
public:
    FixedVector() :
        m_count(0)
    { ; }

    void append(const ELEM & elem)
    {
        assert(!isFull());
        (void)new (&m_elements[m_count++]) ELEM(elem);
    }
    
    void empty()
    {
        while (m_count > 0)
        {
            m_elements[--m_count].~ELEM();
        }
    }

    int getCount() const
    {
        return m_count;
    }

    bool isEmpty() const
    {
        return m_count == 0;
    }

    bool isFull() const
    {
        return m_count >= MAX_COUNT;
    }

protected:
    int m_count;
    union
    {
        ELEM m_elements[0];
        char m_storage[sizeof(ELEM) * MAX_COUNT];
    };
};
```

同時使用FixedVector與C++內的vector時
```c=
void reverseToFixedVector(
    vector<int> * source,
    FixedVector<int, 8> * dest)
{
    dest->empty();
    while (!source->empty())
    {
        if (dest->isFull())
            break;

        dest->append(source->back());
        source->pop_back();
    }
}
```
* 第一個empty是清空dest這個向量，第二個則會檢查source這個向量是不是空的

### 不要搬石頭砸自己的腳
功能：把一堆視窗，用行列的排列方式塞進一個矩形區塊中，讓矩形區塊大致上保持長寬相同的比例
```c=
int split(int min, int max, int index, int count)
{
    return min + (max - min) * index / count;
}

void split(int x0, int x1, int y0, int y1, int &r0, int &r1)
{
    r0 = split(x0, x1, y0, y1);
    r1 = split(x0, x1, y0 + 1, y1);
}

void layoutWindows(vector<HWND> ww, LPRECT rc)
{
    int w = ww.size();
    int rowCount = int(sqrt(float(w - 1))) + 1;
    int extra = rowCount * rowCount - w;
    int r = 0, c = 0;
    HWND hWndPrev = HWND_TOP;
    for (HWND theWindow : ww)
    {
        int cols = (r < extra) ? rowCount - 1 : rowCount;
        int x0, x1, y0, y1;
        split(rc->left, rc->right, c, cols, x0, x1);
        split(rc->top, rc->bottom, r, rowCount, y0, y1);
        SetWindowPos(
            theWindow,
            hWndPrev,
            x0,
            y0,
            x1 - x0,
            y1 - y0,
            SWP_NOZORDER);
        hWndPrev = theWindow;
        if (++c >= cols)
        {
            c = 0;
            ++r;
        }
    }
}
```
* 調用split時x0與x1是視窗相應矩形左右兩側的座標。但在split函式內部x0與x1卻是不同意思（在A函式內是一般變數，但在B函式內是笛卡爾座標

```c=
int divideRange(int min, int max, int index, int count)
{
    return min + (max - min) * index / count;
}

void layoutWindows(vector<HWND> windows, LPRECT rect)
{
    int windowCount = windows.size();
    int rowCount = int(sqrt(float(windowCount - 1))) + 1;
    int shortRowCount = rowCount * rowCount - windowCount;

    HWND lastWindow = HWND_TOP;
    int rowIndex = 0, colIndex = 0;

    for (HWND window : windows)
    {
        int colCount = (rowIndex < shortRowCount) ?
                       rowCount - 1 : 
                       rowCount;

        int left = divideRange(
            rect->left,
            rect->right,
            colIndex,
            colCount);
        int right = divideRange(
            rect->left,
            rect->right,
            colIndex + 1,
            colCount);
        int top = divideRange(
            rect->top,
            rect->bottom,
            rowIndex,
            rowCount);
        int bottom = divideRange(
            rect->top,
            rect->bottom,
            rowIndex + 1,
            rowCount);

        SetWindowPos(
            window,
            lastWindow,
            left,
            top,
            right - left,
            bottom - top,
            SWP_NOACTIVATE);

        lastWindow = window;

        if (++colIndex >= colCount)
        {
            colIndex = 0;
            ++rowIndex;
        }
    }
}
```
* 容易追朔正在發生的事情
* 一看就可以知道變數所代表的概念，不用藉由程式碼去推斷其含義
* 類似的東西，類似的名稱。相同的東西，相同的名稱。

### 不要讓我花力氣去想
* 匈牙利命名標準（以ruby為例）:早期C編譯器在進行連結時對於型別的檢查不夠嚴謹，因此創立出這種補救方法（避免型別錯誤）
```ruby=
user = User.new
objUser = User.new


def create
  @user = User.create(name: params[:name], age: params[:age])
end

def create
  strName = params[:name]
  intAge = params[:age]
  @objUser = User.create(name: strName, age: intAge)
end

def show
  @intUserId = params[:id]
  @objUser = User.find(@intUserId)
end
```