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