iT邦幫忙

2024 iThome 鐵人賽

DAY 23
0
Modern Web

與 AI 一起開發 Side Project 吧!系列 第 23

Day23 — 青出於藍 | 淺談「識別壞味道」,分辨程式碼的好壞

  • 分享至 

  • xImage
  •  

前言

程式碼臭臭?

何為程式碼異味? 程式碼又是如何散發「臭味」?

在「重構:改善既有程式的設計」 這本書有提到,程式碼異味(Code Smell)是在講說,程式碼異味代表著一個跡象:「這裡有個可以用重構來解決的問題」。

而異味本身並不代表「問題」,而是問題的指標。或者你可以這麼想,有問題的不是刺鼻的臭味,而是那堆散發臭味的垃圾。

Bob 大叔在他的部落格提到:「我看到一次超過十行的 Java 程式碼,鼻頭就會開始皺起來」,這就是一個很經典,相當直覺聞到異味的跡象,接下來我們就先來看看,到底我們的程式碼有什麼樣的「異味」。

一些壞味道

異味清單

從 Code Smell 的清單參考幾個來看看 🚭 參考了常見的前 10 名異味清單,如下所示:

  1. Duplicated Code 重複程式碼: 相同或相似的程式碼在多個地方出現。
  2. Long Method 長方法: 方法過長,難以理解和維護。
  3. Large Class -- 大 Clas: 類別過於龐大,承擔過多責任。
  4. Long Parameter List: 方法參數過多,影響可讀性。
  5. Feature Envy: 一個類別過度依賴另一個類別的數據。
  6. Data Clumps: 一組數據經常一起出現,應該封裝成一個物件。
  7. Primitive Obsession: 過度使用基本數據類型,應考慮使用物件。
  8. Switch Statements: 使用多個 switch 語句,應考慮使用多態性(多型)。
  9. Temporary Field: 類別中有些屬性在某些情況下才會被使用。
  10. Inappropriate Intimacy: 類別之間過度依賴,應減少耦合。

以上 10 個是常見的 Code Smell 清單,10 個是有點多,看到有點眼花。其中有幾項蠻接近的,因此我整理了一下,大概可以分成這 3 類異味:

  1. 避免重複:DRY 原則
  2. 避免過大、過長、(職責)過多的程式
  3. 善用物件導向:封裝、耦合還有多態(避免過多的 if / else / switch)。

AI 已經寫得不錯了

鏡頭轉回棚內,回頭看一下 App 的程式碼,先用肉眼掃描一下,有沒有 Bob 大叔提到的,一看到就會捏起鼻子的 Code 呢?

看了一眼,其實沒有想像中的那麼多 😂 

說真的,請 AI 產出程式碼,已經避免掉很多常見的程式碼異味問題,不太會犯初學者常犯的那些錯誤。比方說,不太會有程式碼都寫成一坨的義大利麵程式碼(Spaghetti code)問題發生。AI 生成的結果,倒是結構頗為分明,每個組件打從一出生,就清清楚楚被分配好職責。

https://ithelp.ithome.com.tw/upload/images/20241003/20168308VPqmSuLoEC.jpg

基本的壞味道

不過,雖然肉眼看不出來,那還是問一下 AI 的意見好了。雖然程式碼也是它寫出來的,但 LLM 不會像人類那樣有「宜家效應」,覺得自己寫的 Code 比較好而抗拒調整。而且你可以想像說,每次回答,都是不同的回答者在回答,所以要多問幾次。

此外,不同 LLM 模型之間的回答差異會更大,所以多問幾次,就可以把結果逼近取得最大的交集,以取得最好的結果。

試著請 claude-sonnet-3.5 模型給一些建議:

總觀這個程式碼檔案,你可以看出有哪些 Code Smell? 並提供一些修改建議,謝謝。

AI 瞄了程式碼檔案,瞬間給了以下回覆

1. 組件過大:
   `App` 組件太大且複雜,包含了太多的狀態和邏輯。這可能會導致維護困難。
   - 建議:將 `App` 組件拆分成更小的、可重用的組件。例如,可以創建一個單獨的 `Calculator` 組件來處理計算邏輯。

2. 重複的樣式代碼:
   在多個地方重複使用了相似的樣式類。
   - 建議:創建一個共用的樣式文件或使用 CSS-in-JS 解決方案來減少重複。

3. 硬編碼的字符串:
   類別名稱和按鈕文本直接寫在代碼中。
   - 建議:將這些字符串提取到常量或配置文件中,以便於國際化和維護。

4. 未使用的函數:
   `calculateFoodCatogoryCount` 函數被定義但從未使用。
   - 建議:如果不需要,刪除這個函數;如果需要,確保在適當的地方使用它。

5. 複雜的條件渲染:
   在 `App` 組件中使用了條件渲染來切換 `CategorySelector` 和 `Keypad`。
   - 建議:考慮使用狀態機或路由來管理這種複雜的 UI 狀態。

6. 重複的邏輯:
   處理編輯和添加新項目的邏輯有些重複。
   - 建議:將這些邏輯提取到單獨的函數中,以減少重複並提高可讀性。

7. 缺乏錯誤處理:
   沒有看到明顯的錯誤處理邏輯,例如處理無效輸入。
   - 建議:添加適當的錯誤處理和用戶反饋機制。

8. 過多的內聯樣式:
   有些組件,如 `Keypad`,包含了大量的內聯樣式邏輯。
   - 建議:考慮使用 CSS 模塊或 styled-components 來更好地組織樣式。

9. 缺乏類型檢查:
   雖然使用了 TypeScript,但有些地方可以改進類型定義,例如 `HistoryItem` 類型。
   - 建議:確保所有的類型都被正確定義和使用,以充分利用 TypeScript 的優勢。

10. 狀態管理複雜:
    `App` 組件中有多個相互關聯的狀態,這可能導致難以追踪狀態變化。
    - 建議:考慮使用 Redux 或 Context API 來集中管理狀態,特別是如果應用程序進一步擴大。

綜觀以上的建議,有些是因為還在開發途中(寫到一半還沒整理好),還在持續調整,撇除這些因素的程式碼,整理出目前程式碼具有「異味」的部分:

  • 重複邏輯
  • 狀態管理複雜
  • 缺乏錯誤處理

還有哪些味道?

從上到下「晃了一下」程式碼,發覺有幾個地方不是很好理解(臭臭!捏起鼻子)。

首先是這一塊,一個深層巢狀結構,光看就有點反胃(嘔)。雖然很清楚這一塊在做什麼,但是哪天要修改其中一個按鈕的樣式,我會被這塊邏輯給嚇到,愣在那邊不知該如何動刀 🥸。

<button
    key={index}
    className={`p-4 text-xl font-bold rounded ${
        btn === "AC"
            ? "bg-orange-500 text-white"
            : btn === "⌫"
                ? "bg-yellow-500 text-white"
                : btn === "+"
                    ? "bg-gray-600 text-white"
                    : "bg-gray-700 text-white"
    }`}
    //...
//...
onClick={() => {
    if (btn === "AC") onClear();
    else if (btn === "⌫") onBackspace();
    else onInput(btn);
}}

在往下看一點,上面這一塊負責觸發行為的程式碼,也是有類似的問題。剛好蠻像上面提到「物件導向」那一種異味的「善用多態」,但先留著,晚點再來動手調整 👍

最後,還有一個地方如 AI 回覆的那樣,不過我關注的點不是寫了比較多的 if / else ,而是 if 的嵌套邏輯有點難懂,兩層的 if 在其中,若無法避免這樣寫 if / else,是不是可以讓程式邏輯更好懂一點?

所以我覺得這邊也有點臭臭的,但還不急著動手,但可以等晚點將邏輯(職責)拆分出去後,說不定 if / else 就消失了

const handleSelectCategory = (category: string) => {
    if (pendingAmount !== null) {
        if (editingItem) {
            // Edit existing item
            setHistory(history.map(item => 
                item.id === editingItem.id ? { ...item, amount: pendingAmount, category } : item
            ));
            setEditingItem(null);
        } else {
            // Add new item
            const newItem = { amount: pendingAmount, category, id: Date.now() };
            setHistory([...history, newItem]);
        }
        setPendingAmount(null);
    }
    setShowCategorySelector(false);
};

那你會想說,程式碼異味有沒有一個簡單判斷的通則?算是有吧,想分享一個蠻簡單的方法,來判斷程式碼是不是有異味:

如果回頭看了三番兩次,還不是很理解,需要請 AI 解釋的話(或是請別人幫忙解說),代表寫得不夠清楚,此段程式八成就是散發著異味了。


尾聲

異味與重構

重構:改善既有程式的設計一書中,最後的附錄表列了「不只 10 種」的異味清單,且每種 Code Smell 都有對應的重構方法,蠻推薦大家去讀的喔,相當經典的重構好書。

接下來,從這些異味點(Code Smell Spots)出發,著手進行重構囉!

REF


上一篇
Day22 — 青出於藍 | 稍微暫停,整理重構一下程式碼吧
下一篇
Day24 — 青出於藍 | 跟 AI 一起動手重構,小步批次重構
系列文
與 AI 一起開發 Side Project 吧!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言