← 開發日常

避免隱晦的程式邏輯

Article image

在開發中,我們為了完成某項功能的細部行為,可能會有很多種做法,如何評估哪種作法更好呢?其中一項準則就是清楚呈現邏輯。當程式碼能夠清楚呈現邏輯時,往後其他人在閱讀程式碼時,就能更快的了解其意圖。

就同如《先整理一下?》所提到的:「作者只有一個,但讀者通常會有很多個」,如果把邏輯用隱晦的方式完成,我們很難保證每個人對都能理解這段邏輯的意圖,而呈現意圖也是 Kent Beck 簡單設計中的一項原則。

舉個例子

假設畫面有兩個彈跳視窗 A 與 B,這兩個彈跳視窗有些規則,規則如下:

  • 系統會先顯示彈跳視窗 A,接著才顯示彈跳視窗 B。
  • 如果使用者已經看過了彈跳視窗 A,則 A 此後都不再顯示
  • 如果使用者今天看過了彈跳視窗 B,則 B 今天不再顯示

根據這三個規則,我們可以很容易地寫出類似下方這樣的程式碼:

( 這裡讓我們先暫時忽略持久化的部分,畢竟這不是重點 )

dart
Future<void> showPopups() async {
	if (!hasPopupAShow) {
		await showPopupA();
		hasPopupAShow = true;
	}

	if (popupBShowDate != today()) {
		await showPopupB();
		popupBShowDate = today();
	}
}

這段程式碼也完整地呈現了上述的三的規則。

事情發生了改變

若是今天需求發生了變化,在這兩個彈窗之間,又新增了一條規則

  • 如果使用者今天看過了 A,今天之內就不要再看到 B 了。

那我們應該如何調整呢?觀眾們也可以先想想看,若是自己來處理的話,會怎麼做。

Article image

一種作法

一種作法是,我們可以在看完 A 的時候,順手把 B 的 popupBShowDate 也順手設了,這樣一來,當檢查到 B 的時候,自然也就會不打開 B。這個作法相當簡單,不要改動太多程式碼,只要僅僅多家一行程式碼就能完成,省時又省力。

dart
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。

dart
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 的顯示邏輯。

看完兩種作法之後,不知道你喜歡哪種呢?或者你有更好的作法,歡迎留言交流分享。

小結

無論第一種作法或第二種,我們都能完成功能。在完成功能之後,我們還得花時間看看我們的程式碼,確認程式碼是否符合各種設計原則,透過不斷重構,讓程式碼不斷演化的過程中,能維持品質,支持後續的改動。

年節已到,祝大家新年快樂。