程式碼優化與重構的衡量思路
進行優化與否的判斷
為甚麼要進行程式碼優化與重構
- 既有的程式碼產生了Bug,或是為了要擴展新功能
- 讓同仁或是我自己在開發可以有更好的方式進行開發
- 單純覺得效能跟可讀性可以再更好
甚麼時候不要進行程式碼優化與重構
- 如果舊的程式碼已經達到預期的效果,並且沒有產生Bug
- 既有的邏輯或是型別已經在多個地方被使用,造成相依性太強,無法梳理出究竟會影響到哪裡、會不會產生不可預期的錯誤或是問題
舉例說明
這是根據實際上遇到的問題需求提出的相似解法。
1. 從Array.find 改用 new Map().get()
import { ref } from "vue";
const items = ref<Item[]>([]);
const findItem = (id: string) => {
return items.value.find((item) => item.id === id);
};import { ref, computed } from "vue";
const items = ref<Item[]>([]);
const itemById = computed(
() => new Map(items.value.map((item) => [item.id, item])),
);
const getItemName = (id: string): string => {
const item = itemById.value.get(id);
if (item === undefined) return "";
return item;
};這邊的寫法對照的是同一個需求(依 id 從列表拿出資料),但刻意拆成兩種結構,方便說明我怎麼決定要不要換寫法。
實務上 很多畫面與流程都會載入不小的列表:依客戶規模,常見可能是從數十筆到數千筆資料不等。
第一種保留 ref 裝列表,需要時再 find:實作最短、一眼能懂,資料量小、或查找次數少時,維護成本通常最低。
第二種把 items 仍放在 ref,查詢用的索引改由 computed 建 Map:items 一變,Map 跟著重算一次;之後多次 .get(id) 近似常數時間,能 大幅減少重複查找所花的時間。代價是 多保留一份以 id 為鍵的索引結構,記憶體負擔會比單純陣列略高;但在「筆數多、查找又密集」的情境下,通常仍值得用少量記憶體換流暢度。空值則在函式裡用 if 明確處理成 "",避免上層模板或呼叫端到處寫 optional chaining,顯示路徑也比較一致。
決定要不要換寫法,取決於客戶實際資料量、操作時的查找頻率,以及你是否願意用多一點記憶體與一次性的 Map 建置,換後續大量查找的成本。若情境已經落在「千筆級列表+表單/服務內高頻查 id」,我會傾向第二種;若列表短、查找也少,第一種就夠用,也符合前面說的「舊程式已達預期就不必硬優化」。
請注意!
當 items.value頻繁變化(例如即時更新、WebSocket 推送、表單編輯後 refresh 等)時,每次 items 一變就會重建整個 Map。 在千筆資料下,重建 Map 的成本(O(n))如果太頻繁,可能反而抵消掉 .get() 帶來的優勢,甚至造成短暫卡頓。
2. 減少 API 請求次數
做法一:POST 成功後再打一支列表 GET,把 ref 整包換成最新資料。
const items = ref<Item[]>([]);
const submitCreate = async (payload: CreatePayload) => {
await api.createItem(payload);
// 這裡簡略不做錯誤處理
items.value = await api.fetchItems();
};做法二:POST 成功後,API 回傳新建資料,直接 push 進 items,不再打列表 GET。
const items = ref<Item[]>([]);
const submitCreate = async (payload: CreatePayload) => {
const created = await api.createItem(payload);
// 這裡簡略不做錯誤處理
items.value.push(created);
};這樣做是為了 少打一次 API(省掉成功後那支列表 GET)。因為列表資料一多,整包重拉會比較慢、也較耗流量;既然 POST 已經回傳新建好的那一筆,直接併進 ref 就夠更新畫面,不必再多打一輪。同時也可以降低使用者的等待時間。
小結
以上兩點是我實作與重構時 印象比較深 的例子。若之後還想到其他情境,會再補上後續說明。
