JavaScript 整潔程式碼實踐:提升程式碼品質的具體方法
程式碼品質不只是「能跑就好」這麼簡單,更關乎可讀性、可維護性和團隊協作效率。這篇文章整理了我在 JavaScript 開發中實踐整潔程式碼的一些方法,都是踩過坑後的真實心得。
命名的藝術
有意義的命名
命名是程式設計中最重要卻最容易被忽視的環節。
javascript
// ❌ 不好的命名:無法理解變數用途
const d = new Date()
const arr = []
let flag = true
// ✅ 好的命名:清楚表達意圖
const createdAt = new Date()
const activeUsers = []
let isAuthenticated = true命名的一致性
在專案中保持命名風格的一致性:
javascript
// ❌ 不一致的命名風格
getUserInfo()
fetchUserData()
retrieveUserDetails()
// ✅ 一致的命名風格
getUser()
getUserProfile()
getUserSettings()避免魔術數字
javascript
// ❌ 魔術數字讓人困惑
setTimeout(() => {
// do something
}, 86400000)
// ✅ 使用有意義的常數
const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000
setTimeout(() => {
// do something
}, ONE_DAY_IN_MS)函式設計原則
單一職責原則
一個函式應該只做一件事,並且做好它。
javascript
// ❌ 函式職責過多
function processUser(user) {
// 驗證使用者
if (!user.email || !user.name) {
throw new Error('Invalid user')
}
// 格式化資料
const formattedUser = {
...user,
name: user.name.trim(),
email: user.email.toLowerCase()
}
// 儲存到資料庫
database.save(formattedUser)
// 發送通知
emailService.send(user.email, 'Welcome!')
return formattedUser
}
// ✅ 拆分成多個職責單一的函式
function validateUser(user) {
if (!user.email || !user.name) {
throw new Error('Invalid user')
}
}
function formatUser(user) {
return {
...user,
name: user.name.trim(),
email: user.email.toLowerCase()
}
}
function saveUser(user) {
return database.save(user)
}
function sendWelcomeEmail(email) {
return emailService.send(email, 'Welcome!')
}
// 在需要時組合這些函式
async function registerUser(user) {
validateUser(user)
const formattedUser = formatUser(user)
await saveUser(formattedUser)
await sendWelcomeEmail(formattedUser.email)
return formattedUser
}函式參數的最佳實踐
javascript
// ❌ 參數過多且順序容易混淆
function createUser(name, email, age, country, city, zipCode, phone) {
// ...
}
// ✅ 使用物件參數
function createUser({ name, email, age, address, phone }) {
const { country, city, zipCode } = address
// ...
}
// 呼叫時更清楚
createUser({
name: 'John',
email: 'john@example.com',
age: 30,
address: {
country: 'Taiwan',
city: 'Taipei',
zipCode: '100'
},
phone: '0912345678'
})預設參數與解構
javascript
// ✅ 使用預設參數
function fetchData({
url,
method = 'GET',
timeout = 5000,
headers = {}
}) {
// ...
}
// ✅ 解構時給予預設值
function processConfig(config = {}) {
const {
debug = false,
retries = 3,
cache = true
} = config
// ...
}處理非同步程式碼
從 Callback 到 Async/Await
javascript
// ❌ Callback hell
getUser(userId, (error, user) => {
if (error) {
handleError(error)
return
}
getOrders(user.id, (error, orders) => {
if (error) {
handleError(error)
return
}
processOrders(orders, (error, result) => {
if (error) {
handleError(error)
return
}
console.log(result)
})
})
})
// ✅ 使用 async/await
async function processUserOrders(userId) {
try {
const user = await getUser(userId)
const orders = await getOrders(user.id)
const result = await processOrders(orders)
console.log(result)
} catch (error) {
handleError(error)
}
}錯誤處理的最佳實踐
javascript
// ❌ 吞掉錯誤
async function fetchData() {
try {
const data = await api.get('/data')
return data
} catch (error) {
// 靜默失敗
}
}
// ✅ 適當的錯誤處理
async function fetchData() {
try {
const data = await api.get('/data')
return { success: true, data }
} catch (error) {
console.error('Failed to fetch data:', error)
// 根據錯誤類型決定是否重試或回傳備用資料
return { success: false, error: error.message }
}
}並行處理
javascript
// ❌ 序列執行(慢)
async function fetchAllData() {
const users = await fetchUsers() // 等待 1 秒
const products = await fetchProducts() // 等待 1 秒
const orders = await fetchOrders() // 等待 1 秒
// 總共 3 秒
}
// ✅ 並行執行(快)
async function fetchAllData() {
const [users, products, orders] = await Promise.all([
fetchUsers(),
fetchProducts(),
fetchOrders()
])
// 總共約 1 秒(取最慢的那個)
}物件與陣列操作
不可變性(Immutability)
javascript
// ❌ 直接修改原始資料
function addItem(cart, item) {
cart.items.push(item)
cart.total += item.price
return cart
}
// ✅ 建立新物件
function addItem(cart, item) {
return {
...cart,
items: [...cart.items, item],
total: cart.total + item.price
}
}善用陣列方法
javascript
// ❌ 使用 for 迴圈
const activeUsers = []
for (let i = 0; i < users.length; i++) {
if (users[i].isActive) {
activeUsers.push(users[i].name)
}
}
// ✅ 使用 filter 和 map
const activeUsers = users
.filter(user => user.isActive)
.map(user => user.name)Optional Chaining 與 Nullish Coalescing
javascript
// ❌ 冗長的檢查
const city = user && user.address && user.address.city
const name = user.name !== null && user.name !== undefined
? user.name
: 'Anonymous'
// ✅ 使用現代語法
const city = user?.address?.city
const name = user.name ?? 'Anonymous'模組化與職責分離
合理的檔案結構
javascript
// ❌ 所有功能都在一個檔案
// app.js (1000+ lines)
// ✅ 按功能拆分模組
// services/
// userService.js
// authService.js
// utils/
// validation.js
// formatting.js
// constants/
// config.js匯出的最佳實踐
javascript
// ❌ 預設匯出不利於重構
export default function calculateTotal() {
// ...
}
// ✅ 具名匯出便於追蹤
export function calculateTotal() {
// ...
}
export function calculateTax() {
// ...
}註解與文件
何時需要註解
javascript
// ❌ 註解說明顯而易見的事
// 增加 1
count++
// ❌ 註解彌補爛程式碼
// 這個函式很複雜,需要重構
function process(d) {
// ...非常複雜的邏輯
}
// ✅ 說明為什麼這樣做
// 使用 debounce 避免在快速輸入時頻繁呼叫 API
const searchWithDebounce = debounce(search, 300)
// ✅ 標記已知問題
// TODO: 當使用者權限變更時需要重新驗證 token
// FIXME: 在 Safari 中會有 memory leak,需要調查JSDoc 文件化
javascript
/**
* 計算商品折扣後的價格
* @param {number} price - 原始價格
* @param {number} discount - 折扣百分比 (0-100)
* @returns {number} 折扣後的價格
* @throws {Error} 當價格或折扣為負數時
*/
function calculateDiscountedPrice(price, discount) {
if (price < 0 || discount < 0) {
throw new Error('Price and discount must be positive')
}
return price * (1 - discount / 100)
}避免常見的反模式
1. 過度抽象
javascript
// ❌ 為了抽象而抽象
class AbstractFactoryBuilderManager {
createAbstractFactory() {
return new ConcreteFactoryBuilder()
}
}
// ✅ 保持簡單
function createUser(data) {
return { ...data, createdAt: new Date() }
}2. 全域變數污染
javascript
// ❌ 全域變數
var config = {}
var userData = {}
// ✅ 使用模組或閉包
const app = (() => {
const config = {}
const userData = {}
return {
getConfig: () => config,
getUserData: () => userData
}
})()3. 條件判斷過於複雜
javascript
// ❌ 巢狀過深的條件
if (user) {
if (user.isActive) {
if (user.hasPermission) {
if (!user.isBlocked) {
doSomething()
}
}
}
}
// ✅ 提前返回
if (!user) return
if (!user.isActive) return
if (!user.hasPermission) return
if (user.isBlocked) return
doSomething()
// ✅ 或使用守衛子句
function canPerformAction(user) {
return user?.isActive &&
user.hasPermission &&
!user.isBlocked
}
if (canPerformAction(user)) {
doSomething()
}總結:整潔程式碼的核心原則
- 可讀性優先:程式碼是寫給人看的,機器只是順便執行
- 保持簡單:KISS (Keep It Simple, Stupid) 原則
- 單一職責:每個函式、模組都應該有明確的單一職責
- 測試友善:好的程式碼應該容易測試
- 持續重構:整潔程式碼不是一次到位,而是持續改進的結果
整潔程式碼不是什麼銀彈,也不是要追求完美主義,而是在可讀性、維護性和實用性之間找到平衡。記住一個原則:程式碼是寫給未來的自己和團隊成員看的,讓他們能快速理解並安心修改,這才是整潔程式碼的真正價值。
實務上,我會建議搭配 ESLint 和 Prettier 這類工具來自動化程式碼風格檢查,在 Code Review 時也要把程式碼品質當成重要的審查項目。這樣才能在團隊中慢慢建立起良好的程式碼文化。