---
# System prepended metadata

title: 守則 4 - 先找出三個例子， 才能改用通用的做法

---

# 守則 4 - 先找出三個例子， 才能改用通用的做法

大部分情況下，我們會傾向找出更通用的做法

譬如以下的例子

```cpp=
Sign * findRedSign(const vector<Sign *> & signs)
{
    for (Sign * sign : signs)
        if (sign->color() == Color::Red)
            return sign;

    return nullptr;
}
```

我們可能會想說，上面的程式適用的範圍是否太窄？
或許我們可以讓他支援搜尋其他顏色的號誌

```cpp=
Sign * findSignByColor(const vector<Sign *> & signs, Color color)
{
    for (Sign * sign : signs)
        if (sign->color() == color)
            return sign;

    return nullptr;
}
```

感覺好像還不夠？
說不定某天我們會需要透過「位置、距離、號誌上的文字」來搜尋？
來寫個通用一點的

```cpp=
struct SignQuery
{
    SignQuery() :
        m_colors(),
        m_location(),
        m_distance(FLT_MAX),
        m_textExpression(".*")
    {
        ; 
    }

    bool matchSign(const Sign * sign) const
    {
        return matchColors(m_colors, sign->color()) &&
               matchLocation(m_location, m_distance, sign->location()) &&
               regex_match(sign->text(), m_textExpression);
    }

    vector<Color> m_colors;
    Location m_location;
    float m_distance;
    regex m_textExpression;
};

Sign * findSign(const SignQuery & query, const vector<Sign *> & signs)
{
    for (Sign * sign : signs)
        if (query.matchSign(sign))
            return sign;

    return nullptr;
}

Sign * findRedSign(const vector<Sign *> & signs)
{
    SignQuery query;
    query.m_colors = { Color::Red };
    return findSign(query, signs);
}
```

這樣就可以根據號誌的「顏色、位置、距離、文字」來搜尋了！！！
讚讚！

## YAGNI (You Aren't Gonna Need It)

上面通用的程式，問題在哪裡？

通用程式的撰寫是根據「想象中」的需求而不是「實際」的需求寫出來的。這樣會有什麼問題？
1. 多數的程式碼可能永遠用不到
2. 通用的程式比較複雜增加理解困難
3. 參數怎麽傳遞
4. 通用程式有什麼假設？
5. 通用的程式比較複雜，所以也比較難修改
6. 浪費時間去讓程式符合不存在的需求

下面這個做法，才是找出一個紅色號誌的最佳做法
```cpp=
Sign * findRedSign(const vector<Sign *> & signs)
{
    for (Sign * sign : signs)
        if (sign->color() == Color::Red)
            return sign;

    return nullptr;
}
```

等你拿到了另一個使用案例，再去寫程式解決那個使用案例的情況吧。
請不要嘗試去猜測 ，你所遇到的第二個使用案例會是什麼樣子。
專心寫程式，去解決你能理解的問題就好!

等到有多個類似的案例（超過三）再考慮寫通用的解法

## 比 YAGNI (你根本用不到 )更糟糕的 問題

當我們遇到沒考慮到的實際案例時，想直接套用原本的程式碼會更加困難

譬如以下案例
- 找出一個紅色的號誌
- 在中正路和中山路的轉彎處，找出一個紅色的「停車」號誌 
- (需改程式)找出中正路上所有的紅色或綠色號誌
- (需改程式)找出市民大道或水南路上所有包含「公里」文字的白色號誌
- (需改程式)在磨坊街902號附近找出一個藍色的號誌，或是包含「巷」這個文字的號誌

通用解法改完的程式碼
https://github.com/the-rules-of-programming/examples/blob/5dbda4387361350c886cbb1d2156217c5f2b9cd1/rule4.cpp#L429

問題在哪裡？
1. 因為通用程式很複雜，改動的時候必須小心翼翼
2. 被限制在通用程式的框架下導致程式越改越複雜
3. 當你手裡拿著錘子時，所有的東西看起來都好像是釘子。你會傾向使用通用案例去解決現有的問題，而不是找出更好的方式解決

（上面讓我想到 HasParents#build_with_parents

比較一下：處理兩種做法

```cpp=
void example()
{
    SignQuery * blueQuery = new SignQuery;
    blueQuery->m_colors = { Color::Blue };

    SignQuery * locationQuery = new SignQuery;
    locationQuery->m_areas = { mainStreet };

    SignQuery query;
    query.m_boolean = SignQuery::Boolean::Or;
    query.m_queries = { blueQuery, locationQuery };

    vector<Sign *> locationSigns = findSigns(query, signs);
}
```

這種做法更符合 KISS 的精神，好懂也好維護
```cpp=
vector<Sign *> locationSigns;
for (Sign * sign : signs)
{
    if (sign->color() == Color::Blue ||
        matchArea(mainStreet, sign->location()))
    {
        locationSigns.push_back(sign);
    }
}
```
