跳至內容

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()
}

總結:整潔程式碼的核心原則

  1. 可讀性優先:程式碼是寫給人看的,機器只是順便執行
  2. 保持簡單:KISS (Keep It Simple, Stupid) 原則
  3. 單一職責:每個函式、模組都應該有明確的單一職責
  4. 測試友善:好的程式碼應該容易測試
  5. 持續重構:整潔程式碼不是一次到位,而是持續改進的結果

整潔程式碼不是什麼銀彈,也不是要追求完美主義,而是在可讀性、維護性和實用性之間找到平衡。記住一個原則:程式碼是寫給未來的自己和團隊成員看的,讓他們能快速理解並安心修改,這才是整潔程式碼的真正價值。

實務上,我會建議搭配 ESLint 和 Prettier 這類工具來自動化程式碼風格檢查,在 Code Review 時也要把程式碼品質當成重要的審查項目。這樣才能在團隊中慢慢建立起良好的程式碼文化。

💬 留言討論

Released under the MIT License.