© 2024 Merano Tu. All rights reserved.
Merano Tu
2024/12/19
最近正在讀了一本關於程式設計的書籍:“這樣寫 code 好不好”,書中談到許多容易寫出結構不良程式碼的模式。這些不良模式雖然程式能夠運作,但往往會為後續的維護和擴充帶來諸多困擾。今天想跟大家分享幾個常見的問題和解決方案。
書中主要是用 Java 為例子寫,但我這邊以較熟悉的 Javascript 為例來做整理:
這是指變數、函式或類別的名稱無法清楚表達其用途或含義。
好的命名應該具有自解釋性,讓其他開發者一看就能理解其功能。
ex: 變數或是function 使用簡短字母寫是數字來命名或是疊加
function calc(a, b) {
let t = 0;
let r = [];
for(let i = 0; i < a.length; i++) {
t = a[i] * b;
r.push(t);
}
return r;
}
// 語義化的讓人更容易理解 function 與 變數用意
function multiplyArrayByNumber(numbers, multiplier) {
let currentProduct = 0;
let multipliedNumbers = [];
for(let i = 0; i < numbers.length; i++) {
currentProduct = numbers[i] * multiplier;
multipliedNumbers.push(currentProduct);
}
return multipliedNumbers;
}
過度的條件巢狀會導致程式碼難以閱讀和維護。
這種程式碼結構會使邏輯流程變得複雜且容易出錯。
容易產生的 bug 行為:要執行的事不小心寫在錯誤的判斷層次執行等。
且每次執行時都要跑完所有的判斷,也是挺耗費資源。
function processOrder(order) {
if (order.status === 'active') {
if (order.payment) {
if (order.payment.verified) {
if (order.items.length > 0) {
if (order.shipping.address) {
return processShipment(order);
} else {
return 'No shipping address';
}
} else {
return 'No items in order';
}
} else {
return 'Payment not verified';
}
} else {
return 'No payment info';
}
} else {
return 'Order not active';
}
}
function processOrder(order) {
const errors = {
inactive: 'Order not active',
noPayment: 'No payment info',
unverified: 'Payment not verified',
emptyCart: 'No items in order',
noAddress: 'No shipping address'
};
if (order.status !== 'active') return errors.inactive;
if (!order.payment) return errors.noPayment;
if (!order.payment.verified) return errors.unverified;
if (order.items.length === 0) return errors.emptyCart;
if (!order.shipping.address) return errors.noAddress;
return processShipment(order);
}
指一個模組或類別包含了過多不相關的功能,或是有同類型功能的 function 散裝在各處。
高內聚性意味著一個模組應該只負責一個明確的功能或相關的功能群組。
把所有相關的功能封裝在一個 class 裡面。
封裝
把資料以及操作資料的功能都整理成一個類別,只把必要的程序(也就是函式)對外公開的做法
// 一個類別處理多個不相關的功能
class UserManager {
constructor() {
this.users = [];
}
addUser(user) { /* 用戶管理邏輯 */ }
deleteUser(userId) { /* 用戶管理邏輯 */ }
// 不相關的功能混在一起
generatePDF() { /* PDF 生成邏輯 */ }
sendEmail() { /* 郵件發送邏輯 */ }
processPayment() { /* 支付處理邏輯 */ }
updateInventory() { /* 庫存更新邏輯 */ }
}
// 分離關注點
class UserManager {
constructor() {
this.users = [];
}
addUser(user) { /* 用戶管理邏輯 */ }
deleteUser(userId) { /* 用戶管理邏輯 */ }
}
class DocumentService {
generatePDF() { /* PDF 生成邏輯 */ }
}
class EmailService {
sendEmail() { /* 郵件發送邏輯 */ }
}
class PaymentService {
processPayment() { /* 支付處理邏輯 */ }
}
class InventoryService {
updateInventory() { /* 庫存更新邏輯 */ }
}
這種情況發生在物件被創建後,但必要的屬性或狀態尚未被正確初始化就被使用的情況。
這可能導致運行時錯誤或不可預期的行為。
這種沒有先初始化就無法使用的類別、或是有可能出現未初始化狀態的類別,稱作半熟物件,因為他們無法自立自強。
class User {
constructor() {
this.name = null;
this.email = null;
this.preferences = null;
}
initialize(data) {
this.name = data.name;
this.email = data.email;
this.preferences = data.preferences;
}
}
const user = new User();
// 可能在初始化前就使用物件
someFunction(user); // 危險!物件尚未完整初始化
class User {
constructor(name, email, preferences) {
if (!name || !email) {
throw new Error('Required parameters missing');
}
this.name = name;
this.email = email;
this.preferences = preferences || {};
}
}
// 強制在建立時就提供必要資料
try {
const user = new User('John', 'john@example.com', { theme: 'dark' });
someFunction(user); // 安全!物件已完整初始化
} catch (error) {
console.error('Failed to create user:', error.message);
}
改善這些問題的核心原則:
良好的程式碼結構應該要容易理解、容易測試,且具有明確的職責劃分。