PHP 的高自由度是雙面刃:動態型別、寬鬆語法讓原型開發極快,卻也容易養成「能跑就行」的惡習。經年累月的專案維護證明:真正經得起考驗的代碼,必然是高可讀、低複雜、易重構的。本系列第一部分,聚焦於函數層面的複雜度控制,分享經實戰驗證的重構模式。
人類工作記憶極其有限(約 4±1 個區塊)。函數體過長、巢狀過深、變數過多,都會超出閱讀者的認知頻寬。降低複雜度的本質,是減少閱讀者同時必須記住的資訊量。
將前置條件檢查前置,失敗即返回,主流程保持零縮排:
// ❌ 巢狀地獄
function processOrder(Order $order): Result {
if ($order->isValid()) {
if ($order->hasStock()) {
if ($payment->charge($order->total)) {
// 核心邏輯...
return $result;
}
}
}
throw new InvalidOrderException();
}
// ✅ 衛語句模式
function processOrder(Order $order): Result {
if (!$order->isValid()) throw new InvalidOrderException('訂單無效');
if (!$order->hasStock()) throw new OutOfStockException('庫存不足');
if (!$payment->charge($order->total)) throw new PaymentFailedException('付款失敗');
// 核心邏輯:零縮排,一目了然
return $this->fulfillOrder($order);
}
將判斷邏輯封裝到物件內部,呼叫端只發出指令:
// ❌ 違反封裝:外部檢查狀態再決定行為
if ($user->isActive() && $user->hasPermission('admin')) {
$user->deleteAccount();
}
// ✅ Tell, Don't Ask:封裝決策邏輯
$user->requestAccountDeletion(); // 內部自行判斷權限、狀態、拋異常或執行
將 if-elseif-else 鏈轉為資料驅動,符合開放封閉原則:
// ❌ 每新增格式需修改此函數
function decode(string $type, string $body): Data {
if ($type === 'json') return $this->jsonDecoder->decode($body);
elseif ($type === 'xml') return $this->xmlDecoder->decode($body);
elseif ($type === 'yaml') return $this->yamlDecoder->decode($body);
throw new UnsupportedFormatException($type);
}
// ✅ 映射表:新增格式只需註冊,不修改核心邏輯
private $decoders = [
'json' => JsonDecoder::class,
'xml' => XmlDecoder::class,
'yaml' => YamlDecoder::class,
];
function decode(string $type, string $body): Data {
if (!isset($this->decoders[$type])) {
throw new UnsupportedFormatException($type);
}
$decoder = new $this->decoders[$type]();
return $decoder->decode($body);
}
PHP 7+ 的嚴格型別宣告(declare(strict_types=1))與型別提示,可將大量 if ($x instanceof Foo) 搬移到編譯期:
// ❌ 執行期檢查
function getName($user) {
if ($user instanceof User) return $user->name;
if ($user === null) return 'Guest';
throw new InvalidArgumentException();
}
// ✅ 型別系統保證
function getName(?User $user): string {
return $user?->name ?? 'Guest'; // PHP 8 空值安全運算子
}
將「做一件事」的程式碼區塊提煉為私有靜態方法:
// ❌ 臨時變數鏈,難追蹤
public function capitalizeAndReverse(array $names): array {
$capitalized = array_map('ucfirst', $names);
$capitalizedAndReversed = array_map('strrev', $capitalized);
return $capitalizedAndReversed;
}
// ✅ 函數組合,無臨時變數
public function capitalizeAndReverse(array $names): array {
return self::reverse(self::capitalize($names));
}
private static function capitalize(array $names): array {
return array_map('ucfirst', $names);
}
private static function reverse(array $names): array {
return array_map('strrev', $names);
}
進階形式:將集合操作封裝為值物件,實現鏈式呼叫:
class NameCollection {
private array $names;
public function __construct(array $names) { $this->names = $names; }
public function capitalize(): self { return new self(array_map('ucfirst', $this->names)); }
public function reverse(): self { return new self(array_map('strrev', $this->names)); }
public function toArray(): array { return $this->names; }
}
$result = (new NameCollection([...]))->capitalize()->reverse()->toArray();
陣列應為同質容器,配合 PHPDoc 型別提示與斷言庫:
/** @param DateTime[] $dates */
public function processDates(array $dates): void {
// 靜態分析器與 IDE 直接知道 $date 是 DateTime
foreach ($dates as $date) {
$date->format('Y-m-d'); // 無需 instanceof 檢查
}
}
// 運行期防禦(beberlei/assert)
use Assert\Assertion;
public function processDates(array $dates): void {
Assertion::allIsInstanceOf($dates, DateTime::class);
// ...
}
函數回傳型別不穩定會污染呼叫端:
// ❌ 混合回傳:呼叫端充滿 if-else
function findUser(int $id): User|false {
// ...
}
$user = findUser(1);
if ($user === false) { /* ... */ }
elseif ($user instanceof User) { /* ... */ }
// ✅ 統一回傳:使用 Null Object 或拋異常
function findUser(int $id): User {
$user = $this->repo->find($id);
if (!$user) throw new UserNotFoundException($id);
return $user;
}
// 呼叫端簡潔
try {
$user = findUser(1);
$user->sendNotification();
} catch (UserNotFoundException $e) {
// 統一錯誤處理
}
if (($a || $b) && $c) → if ($this->isEligible($a, $b, $c))if ($obj) → if ($obj instanceof Entity) 或 if ($flag === true)if ('value' === $var) → if ($var === 'value')(現代 IDE 已能偵測賦值錯誤)else 且非必要 → 重構為衛語句降低複雜度不是為了「漂亮」,而是為了降低修改成本、減少缺陷率、加速新人上手。下一篇將深入探討「如何優雅處理 null」與「異常設計模式」。敬請期待。