程式碼品質與重構方法 範例與相關概念

me
林彥成
2020-07-08 | 4 min.
文章目錄
  1. 1. 重構注意事項
  2. 2. 重構的步驟
  3. 3. 重構的方法
  4. 4. 重構專案配置
  5. 5. 重構 Style And Convention
  6. 6. 重構過度結構化
  7. 7. 重構難維護的解決方案

最近幾年常看不同的前端專案,所以想簡單分享成為資深挖洞大師的小秘訣!!!
這篇文章將提到:

  1. 重構的概念
  2. 過往經驗中程式碼的地雷們

重構注意事項

在我們決定重構之前:

  • 有想過所有漸進改善方案?
  • 可階段性發佈漸進改善的成果?
  • 對現有專案考古程度有比另一半多嗎?
  • 重構的時間是否足夠?
  • 重構後有引入後續防呆方法?

當新的寫法能夠不影響原來的功能且讓程式碼本身更健康,重構就應該盡量用最小的單位回到原來的程式碼,原因是:

  • 對原來程式影響較小
  • 對負責 review 的人壓力較小
  • 團隊人數較多的話,少量多餐較容易快速學到新東西
  • Rollback 比較無痛

重構的步驟

  • 讓原來程式碼可以跑起來 (不要笑!!! 很多專案少了些沒進版控的環境檔就跑不起來)
  • 理解原來的行為與結果,重構後才有辦法測試
  • 找到壞味道,就是那些看很多眼還是難以看懂或是不符合基本原則的程式碼們
  • 不過短期改不動且能夠運作的建議就先當成 API 使用,進去出來是穩定的即可 QQ

重構的方法

  1. Red-Green Refactoring
    從單元測試失敗 > 用最簡單的方法讓測試成功 > 用好的方法讓測試成功

  2. Abstraction Refactoring
    多用一層把需要重構的部分包起來,讓新的部分與舊的部分共存,逐步抽換

    • 把這次舊元件想重構的欄位、Method 拉到上層
    • 在上層重構完成後下放到新的元件中
    • 用新元件取代舊元件
  3. Composing Methods Refactoring
    用多個命名更精確的方法去組合一個複雜功能

  4. Moving Features Between Objects Refactoring
    開一個新檔案,逐步把需要的搬過來,順手把不需要的移掉

  5. Simplifying Conditional Expressions Refactoring
    如果是元件做條件渲染,可以用工廠模式或是 hoc 的概念去組出特化的元件

  6. Breaking Code Apart Into More Logical Pieces Refactoring
    原子化的概念,如果單元越小,就越容易進行組合與重複使用

參考文章如下:
https://dzone.com/articles/code-refactoring-techniques

重構專案配置

前端專案設定到進入開發,依照文件設定至少超過半小時

所以我說那個測試機呢? 設定全新機器正式環境且沒 docker 就算正常,之前有遇過前端專案要在 local 環境設定全部的後端開發環境及 DB 資料才可以進行前端開發。

認為比較好的方式是除了整合 branch 的測試環境,後端在測試機上每個人也都有自己的測試環境,這樣前端在進行測試的時候就可以針對不同的 port 切換去做相關測試。舉例來說:

  • 21000: master branch
  • 21001: stage branch
  • 21010: develop branch
  • 21011: feature branch
  • 21020: developer X’s env
  • 21021: developer Y’s env

早期的專案會留下過多需要同時啟動的相關環境,沒有使用 docker 的話 CI/CD 也會相對困難

早期專案容易導入像是 compass 或是 gulp 來幫助自動化,但現在前端專案以 React 來說,使用 create react app 或是 next.js 都是不錯的選項,可以大量減少我們對前端專案配置檔上的維護與後續升級問題。

重構 Style And Convention

專案配置檔沒有 auto format 相關的設定

  • 空白 Tab 傻傻分不清楚
  • 有時候 - 有時候 _ 你會相信腦包沒藥救 QQ

每個人空格換行風格差異過大,會遇到的問題:

  • 改專案的時候需要手動去處理空格換行
  • 在重構的過程中套用自動格式很可能程式碼變更會很多,會建議 code format 完先 commit 一次方便 review
1
2
3
4
5
6
var hello-world = '';
if (hello-world !== ‘helloWorld’) {
return test;
} else {
retrun;
}

該用常數的地方不用常數

我之前曾在長期吵雜的環境下工作,在難以專注的情況下若沒有常數防呆就容易發生錯誤。

1
2
3
4
5
6
7
// '/t/js/lesson/1', '/js/lesson/1' 複製貼上過程容易 GG 且散落各地
Router.push("/t/js/lesson/1");
Router.push("/js/lesson/1");

// 親身經歷下面這樣真的好很多 Orz
const ROUTE_JS_TEST = "/t/js/lesson/1";
Router.push(ROUTE_JS_TEST);

樣式檔或 JS 沒有 convention

陳年的專案經過多人經手,所以有著多種風格邏輯與寫法,沒裝 ESLint 或是沒有套用大家常用的 Rule 像是 eslint-config-airbnb,建議提早導入 ESLint 或是 StyleLint 這類工具。

樣式檔除了導入 BEM 這類命名規則外,如果是用 atomic-css 的概念是不是也順便解決這些問題了? 更進階一點也許可以直接導入 tailwindcss 這類的工具。

1
2
3
4
5
6
7
/* -, _都幾? */
.user-avatar {
display: block;
}
.user_avatar {
display: block;
}

元件沒有寫 Props 驗證
元件傳入的時候又很愛用 ...props 直接把 parent 給的全部一起往下傳

React 文件寫很棒拜託要看:
https://reactjs.org/docs/typechecking-with-proptypes.html

1
2
3
4
5
<OrzComponent {...props}></OrzComponent>;

function OrzComponent(props) {
return <OrzChild {...props} />;
}

重構過度結構化

過度結構化
我泥中有你你泥中有我

工作一段時間之後,總會覺得好像有些地方可以一次到位,但如果需要跟不同程度的人合作的話,就要認真考慮了。

舉例來說像是像是 RX 這類的覺得就不用急著導入,You aren’t going to need it(YAGNI),也就是只需要根據當下的需求,做最簡化的設計。

如果發現結構複雜且會互相影響的元件,我會:

  1. 還沒有更好的切法前,會直接寫在一起,雖然元件可能會變太大 Orz
  2. 用 redux 這類 Single Source of Truth 的工具
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const user = {
basic: {
name: "test",
},
advance: {
password: "***",
},
};
console.log(user.basic.name);

function TestComponent() {
return <Parent>{this.props.children}</Parent>;
}

export default withEventTracking(withFavorites(withAccount(TestComponent)));
1
2
3
4
5
6
7
8
9
10
11
function ModuleA() {
setStateInModuleB();
setStateInModuleC();
}
function ModuleB() {
setStateInModuleA();
}
function ModuleC() {
setStateInModuleA();
setStateInModuleB();
}

重構難維護的解決方案

用冷門且有點學習、維護門檻的解決方案

覺得如果還年輕,沒有職場壓力的前提下,還是用好維護且容易的方式解決問題,這才是培養競爭力 QQ 之前有看過比較少人用且可能比較難維護的:

  • redux-orm
  • lua
  • 魔改過的 create-app-script

使用無意義或很難搜尋的的敘述或縮寫來做判斷條件

這個真的就是只能逐步慢慢改,初期可能還是把 call function 當 call API 來用就好了 Orz

1
2
3
4
5
6
7
8
9
10
11
// conditions[3] 你好 9527
if (conditions[3].value === "9527") {
}

// 看懂暗號只需要一個眼神,所以是 signal 還是 show girl?
switch (sg === "9487") {
case "ccc":
break;
case "www":
break;
}

喜歡這篇文章,請幫忙拍拍手喔 🤣


share