# 守則 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);
}
}
```