程式碼不僅要正確,還要高效。本章探討如何編寫經濟的程式碼,在效能、可維護性和開發成本之間取得平衡。

為什麼需要經濟的程式碼?#

效能的價值#

程式碼的效能直接影響:

  • 用戶體驗
  • 運營成本
  • 系統容量
  • 業務競爭力

效能不只是技術問題#

效能問題 = 意識問題 + 見識問題 + 技術問題
            80%         15%          5%

大多數效能問題不是因為技術難度高,而是因為:

  • 沒有意識到需要考慮效能
  • 不知道有更好的解決方案
  • 沒有養成效能導向的思維

什麼時候考慮效能?#

越早越好,而不是越晚越好。

不要相信:「我們只有一萬個用戶,不需要考慮效能。」

問自己:

  • 這一萬用戶會同時訪問嗎?
  • 會有惡意攻擊嗎?
  • 程式碼帶來的絕對價值有多大?

高效程式碼的思考框架#

效能的本質#

程式碼的效能不是計算速度,而是資源管理

  • 記憶體
  • CPU
  • 磁碟 I/O
  • 網路 I/O

效能最佳化的層次#

架構層面(影響 80%)
    └── 選擇正確的技術堆疊和架構

演演算法層面(影響 15%)
    └── 選擇合適的資料結構和演演算法

程式碼層面(影響 5%)
    └── 微觀最佳化

先在高層次最佳化,再考慮低層次最佳化。

避免過度設計#

過度設計的表現#

  • 為不存在的需求預先設計
  • 過度抽象和封裝
  • 引入不必要的中間層
  • 盲目應用設計模式

YAGNI 原則#

You Ain’t Gonna Need It - 你不會需要它

// 過度設計:目前只有一種支付方式
interface PaymentStrategy { }
class PaymentStrategyFactory { }
class AbstractPaymentHandler { }
class WechatPaymentStrategy implements PaymentStrategy { }

// 簡單直觀:等需要時再擴展
class PaymentService {
    void pay(Order order) {
        wechatApi.pay(order);
    }
}

平衡擴展性與簡單性#

預留擴展點,但不提前實作:

// 使用介面,但不提前實作多個實作類別
public interface ConfigSource {
    String getValue(String key);
}

// 目前只有一個實作
public class RedisConfigSource implements ConfigSource {
    // ...
}

// 需要時再添加
// public class ZookeeperConfigSource implements ConfigSource { }

簡單直觀的設計#

簡單是最高的智慧#

越是能用簡單的方法解決複雜的問題,越能體現一個人的能力。

介面設計原則#

1. 參數少而精

// 不好:參數過多
void createUser(String name, String email, String phone,
                String address, String city, String country);

// 好:使用參數物件
void createUser(UserCreateRequest request);

2. 命名直觀

// 不好:需要查文件才知道做什麼
void process(int type, String data, int flag);

// 好:自解釋
void sendEmailNotification(String recipientEmail, String message);

3. 行為可預測

// 不好:有副作用
int getCount() {
    count++;  // 副作用!
    return count;
}

// 好:純函式
int getCount() {
    return count;
}

記憶體使用最佳化#

物件創建的成本#

Java 物件創建涉及:

  1. 記憶體分配
  2. 建構函式執行
  3. 垃圾回收壓力

最佳化策略#

1. 物件池化

// 頻繁創建銷毀的物件考慮池化
ExecutorService executor = Executors.newFixedThreadPool(10);

// 資料庫連線池
DataSource dataSource = new HikariDataSource(config);

2. 避免不必要的物件創建

// 不好:每次呼叫都創建新物件
public boolean isValidEmail(String email) {
    return email.matches("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$");
    // matches() 每次都編譯正則表達式
}

// 好:預編譯
private static final Pattern EMAIL_PATTERN =
    Pattern.compile("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$");

public boolean isValidEmail(String email) {
    return EMAIL_PATTERN.matcher(email).matches();
}

3. 使用基本型別

// 不好:不必要的裝箱
Integer count = 0;
for (int i = 0; i < 1000000; i++) {
    count = count + 1;  // 大量裝箱拆箱
}

// 好:使用基本型別
int count = 0;
for (int i = 0; i < 1000000; i++) {
    count++;
}

4. 選擇合適的集合

場景推薦集合
隨機存取ArrayList
頻繁插入刪除LinkedList
去重HashSet
有序去重TreeSet
鍵值對HashMap
有序鍵值對TreeMap
執行緒安全ConcurrentHashMap

延遲分配的兩面性#

什麼是延遲分配?#

只在真正需要時才分配資源。

// 立即分配
private List<Item> items = new ArrayList<>();

// 延遲分配
private List<Item> items;

public List<Item> getItems() {
    if (items == null) {
        items = new ArrayList<>();
    }
    return items;
}

延遲分配的好處#

  1. 節省資源 - 不使用的物件不創建
  2. 加快啟動 - 延後初始化時間

延遲分配的問題#

  1. 執行緒安全 - 需要額外同步
  2. 增加複雜度 - 空值檢查
  3. 隱藏問題 - 初始化例外延後發現

除非有明確的效能需求,否則優先使用立即分配。

非同步事件處理#

為什麼使用非同步?#

同步處理:
用戶請求 → 處理 A → 處理 B → 處理 C → 回應
         [===============================]
              等待時間長

非同步處理:
用戶請求 → 處理 A → 回應
              └→ 非同步處理 B
                  └→ 非同步處理 C
         [========]
         快速回應

常見非同步模式#

1. 執行緒池

ExecutorService executor = Executors.newFixedThreadPool(10);

executor.submit(() -> {
    // 非同步任務
    sendEmail(user);
});

2. 訊息佇列

// 發送訊息
messageQueue.send(new OrderCreatedEvent(orderId));

// 消費者處理
@MessageListener
void handleOrderCreated(OrderCreatedEvent event) {
    // 處理訂單創建事件
}

3. CompletableFuture

CompletableFuture.supplyAsync(() -> fetchUserInfo(userId))
    .thenApply(user -> enrichUserData(user))
    .thenAccept(user -> saveToCache(user))
    .exceptionally(ex -> {
        logger.error("Error", ex);
        return null;
    });

非同步的注意事項#

  • 例外處理更複雜
  • 除錯困難
  • 需要考慮重試和冪等性

效能陷阱#

常見效能陷阱#

1. N+1 查詢

// 不好:N+1 次資料庫查詢
List<Order> orders = orderDao.findAll();
for (Order order : orders) {
    User user = userDao.findById(order.getUserId()); // N 次查詢
}

// 好:批次查詢
List<Order> orders = orderDao.findAll();
Set<Long> userIds = orders.stream()
    .map(Order::getUserId)
    .collect(Collectors.toSet());
Map<Long, User> users = userDao.findByIds(userIds); // 1 次查詢

2. 字串拼接

// 不好:大量字串拼接
String result = "";
for (int i = 0; i < 10000; i++) {
    result += i;  // 每次創建新字串
}

// 好:使用 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append(i);
}
String result = sb.toString();

3. 同步塊過大

// 不好:整個方法同步
public synchronized void process() {
    // 非關鍵程式碼
    prepareData();

    // 關鍵程式碼
    updateSharedState();

    // 非關鍵程式碼
    cleanup();
}

// 好:最小化同步範圍
public void process() {
    prepareData();

    synchronized (this) {
        updateSharedState();
    }

    cleanup();
}

4. 不當的快取使用

// 不好:無限制快取
Map<String, Object> cache = new HashMap<>();

// 好:使用有容量限制的快取
LoadingCache<String, Object> cache = CacheBuilder.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build(key -> loadFromDb(key));

可持續發展的程式碼#

技術債務#

不良的設計決策會累積「技術債務」,需要在未來「償還」。

當前開發速度   技術債務增加
     ↓              ↓
    快速    ────────→   長期維護成本高
     │                     │
     └── vs ───────────────┘
     │                     │
    穩健    ────────→   長期維護成本低
     ↓              ↓
 當前開發較慢    技術債務少

平衡策略#

  1. 持續重構 - 不讓技術債務累積
  2. 自動化測試 - 保護重構安全
  3. 程式碼審查 - 及早發現問題
  4. 文件化決策 - 記錄為什麼這樣設計

盡量不寫程式碼#

最好的程式碼是不需要寫的程式碼。

複用現有解決方案#

  1. 使用標準庫 - 不要重複造輪子
  2. 使用開源框架 - 站在巨人肩膀上
  3. 使用雲服務 - 不自建基礎設施

減少程式碼量的方法#

// 冗長
if (str != null && !str.isEmpty()) {
    // ...
}

// 簡潔(使用工具類別)
if (StringUtils.isNotEmpty(str)) {
    // ...
}

// 冗長
List<String> names = new ArrayList<>();
for (User user : users) {
    names.add(user.getName());
}

// 簡潔(使用 Stream)
List<String> names = users.stream()
    .map(User::getName)
    .collect(Collectors.toList());

經濟程式碼檢查清單#

效能檢查清單

設計層面

  • 選擇了合適的架構
  • 沒有過度設計
  • 預留了擴展點

記憶體

  • 避免不必要的物件創建
  • 使用合適的集合類型
  • 設定合理的初始容量
  • 及時釋放大物件

I/O

  • 使用批次操作
  • 使用快取
  • 考慮非同步處理
  • 避免 N+1 問題

並行

  • 最小化同步範圍
  • 使用適當的鎖
  • 考慮無鎖方案

程式碼品質

  • 使用標準庫而非自己實作
  • 程式碼簡潔清晰
  • 有適當的測試覆蓋

總結#

經濟的程式碼 = 正確 + 高效 + 可維護

  1. 效能意識 - 從設計階段就考慮效能
  2. 適度最佳化 - 不過度設計,不過早最佳化
  3. 持續改進 - 通過持續重構保持程式碼品質
  4. 權衡取捨 - 在效能、可讀性、開發成本之間平衡