在開發中,我們為了完成某項功能的細部行為,可能會有很多種做法,如何評估哪種作法更好呢?其中一項準則就是清楚呈現邏輯。當程式碼能夠清楚呈現邏輯時,往後其他人在閱讀程式碼時,就能更快的了解其意圖。
就同如《先整理一下?》所提到的:「作者只有一個,但讀者通常會有很多個」,如果把邏輯用隱晦的方式完成,我們很難保證每個人對都能理解這段邏輯的意圖,而呈現意圖也是 Kent Beck 簡單設計中的一項原則。
舉個例子
假設畫面有兩個彈跳視窗 A 與 B,這兩個彈跳視窗有些規則,規則如下:
- 系統會先顯示彈跳視窗 A,接著才顯示彈跳視窗 B。
- 如果使用者已經看過了彈跳視窗 A,則 A 此後都不再顯示。
- 如果使用者今天看過了彈跳視窗 B,則 B 今天不再顯示。
根據這三個規則,我們可以很容易地寫出類似下方這樣的程式碼:
( 這裡讓我們先暫時忽略持久化的部分,畢竟這不是重點 )
Future<void> showPopups() async {
if (!hasPopupAShow) {
await showPopupA();
hasPopupAShow = true;
}
if (popupBShowDate != today()) {
await showPopupB();
popupBShowDate = today();
}
}
這段程式碼也完整地呈現了上述的三的規則。
事情發生了改變
若是今天需求發生了變化,在這兩個彈窗之間,又新增了一條規則
- 如果使用者今天看過了 A,今天之內就不要再看到 B 了。
那我們應該如何調整呢?觀眾們也可以先想想看,若是自己來處理的話,會怎麼做。
一種作法
一種作法是,我們可以在看完 A 的時候,順手把 B 的 popupBShowDate 也順手設了,這樣一來,當檢查到 B 的時候,自然也就會不打開 B。這個作法相當簡單,不要改動太多程式碼,只要僅僅多家一行程式碼就能完成,省時又省力。
Future<void> showPopups() async {
if (!hasPopupAShow) {
await showPopupA();
hasPopupAShow = true;
popupBShowDate = today();
}
if (popupBShowDate != today()) {
await showPopupB();
popupBShowDate = today();
}
}
這種作法乍聽起來也很自然:看了 A 之後,就當作 B 也看過了,但若是仔細想想,真的是這樣嗎?
但若是對於作者以外的人來說,如果對這個功能不是很了解,在爬 Code 看到這段的時候,就可能很難聯想為什麼 A 看完之後,要同時把 B 的時間也設定了,也就更難推論出當初的作法:看過 A 之後,今天之內就不要再看到 B 了 的行為。
另一種作法
讓我們來看看另外一種作法:把 A 的彈窗顯示紀錄從 bool 改成 DateTime,當看過 A 之後,紀錄 A 看過的時間。
接著我們就在能檢查 B 彈窗的時候,確認只有在今天都還沒看過 A 與 B 兩個彈窗時,才顯示 B。
Future<void> showPopups() async {
if (popupAShowDate == null) {
await showPopupA();
popupAShowDate = today();
}
if (popupBShowDate != today() && popupAShowDate != today()) {
await showPopupB();
popupBShowDate = today();
}
}
相比於第一個作法,我們可以更清楚在邏輯中展示 A 與 B 之間的關係,向讀者說明若是今天看過 A,就不要再顯示 B 了。
雖然第二種寫法看起來比較囉唆,需要的改動也比較多,但是他能更準確地從 if 條件中看出 B 的顯示邏輯。
看完兩種作法之後,不知道你喜歡哪種呢?或者你有更好的作法,歡迎留言交流分享。
小結
無論第一種作法或第二種,我們都能完成功能。在完成功能之後,我們還得花時間看看我們的程式碼,確認程式碼是否符合各種設計原則,透過不斷重構,讓程式碼不斷演化的過程中,能維持品質,支持後續的改動。
年節已到,祝大家新年快樂。