ThisWeb Logo
This.Web
所有文章React 效能優化實戰課
  1. 首頁
  2. 所有文章
  3. AI
  4. 5 種優良的程式設計原則 - AI 時代下的必學心法

5 種優良的程式設計原則 - AI 時代下的必學心法

AI

ThisWeb

資深前端工程師

發佈/更新於

2026年6月2日

免費訂閱電子報!

和 2000+ 工程師一起學習軟體、AI 開發技巧,每週一收穫 1 篇技術內容、1 段職涯分享、1 個最新資訊!

免費訂閱電子報!

和 2000+ 工程師一起學習軟體、AI 開發技巧,每週一收穫 1 篇技術內容、1 段職涯分享、1 個最新資訊!

我認為在 AI 時代下,軟體開發的原則反而變得更加重要。

因為如果不懂這些開發原則,放任 AI 自主開發,就會導致整個程式碼快速膨脹,甚至變得雜亂無章,遲早會累積出一個沒人敢動的爛攤子。

在 Code Complete 這本書中,就有提到幾種優良的程式設計原則,這篇文章挑了 5 個我覺得最實用的,並附上範例讓你對照。

程式設計原則一:最小複雜度

Code Complete 對最小複雜度的定義是:設計應該讓你在專注某一個部分時,能安全地忽略其他大多數部分。

如果你打開一個檔案,腦中要同時處理的東西越少,這個設計就越成功。

也因此,我們其實要避免那些過於聰明的設計,因為聰明的設計通常很難理解,真正好的設計反而是看起來簡單、直白的

以建立訂單的 API handler 為例:

ts
export async function postOrder(request: Request) {
  const body = await request.json();

  if (!body.userId || !body.items || body.items.length === 0) {
    return Response.json({ ok: false, message: '缺少欄位' }, { status: 400 });
  }

  const user = await db.query.users.findFirst({
    where: eq(users.id, body.userId),
  });

  if (!user) {
    return Response.json(
      { ok: false, message: '找不到使用者' },
      { status: 404 }
    );
  }

  const payment = await stripeClient.charge(body.cardToken, body.amount);

  const order = await db
    .insert(orders)
    .values({
      id: nanoid(),
      userId: body.userId,
      amount: body.amount,
      paymentId: payment.id,
    })
    .returning();

  await resend.emails.send({
    to: user.email,
    subject: '訂單成立',
  });

  return Response.json({ ok: true, orderId: order[0].id });
}

這個 handler 同時負責驗證、查資料庫、處理付款、寫入訂單、寄通知信五件事。

任何一個環節要改,都得打開這個函式,而且改動時要小心不要影響其他環節。

ts
export async function postOrder(request: Request) {
  const body = await request.json();
  const parsed = newOrderSchema.safeParse(body);

  if (!parsed.success) {
    return Response.json({ ok: false, message: '表單錯誤' }, { status: 400 });
  }

  const result = await createOrder(parsed.data);

  return Response.json(result, { status: result.ok ? 201 : 400 });
}

handler 只負責解析 HTTP 請求和回傳結果,建立訂單的流程交給 createOrder 函式處理,他介於 HTTP 層和資料庫層之間的業務邏輯層,並不關心資料從哪裡來或要回傳什麼格式,只負責操作具體要做哪些事情。

這樣讀 handler 時就不需要理解付款邏輯,讀 createOrder 時不需要理解 HTTP 格式,每個地方都只需要知道自己該知道的事。

程式設計原則二:高扇入、High Fan-in

Code Complete 的定義是:同一個底層模組被越多地方使用,代表設計越健康。

簡單說就是共通邏輯要收在同一個地方,不要各處各寫一份。

底層的東西被越多人用,代表你做的抽象是有價值的,而不是每個人各自造了一個輪子。

以訂單建立的驗證規則為例:

ts
export async function createOrderFromCheckout(input: unknown) {
  return checkoutOrderSchema.parse(input);
}

export async function createOrderFromAdmin(input: unknown) {
  return adminOrderSchema.parse(input);
}

export async function createOrderFromApi(input: unknown) {
  return apiOrderSchema.parse(input);
}

三個入口各自定義了三套幾乎相同的 schema,只要驗證規則需要調整,就得同時修改三個地方,容易改漏或改不一致。

所以應該抽離成一個驗證規則,讓彼此共用:

ts
export const orderSchema = z.object({
  userId: z.string(),
  items: z.array(z.object({ productId: z.string(), quantity: z.number() })),
  cardToken: z.string(),
});
ts
orderSchema.parse(checkoutInput);
orderSchema.parse(adminInput);
orderSchema.parse(apiInput);

這樣之後驗證邏輯有任何調整,只需要改一個地方。

筆記

這邊也想提醒一下,共用的前提是這些邏輯真的屬於同一件事,如果三個入口的驗證規則只是巧合相同,未來業務發展可能各自走向不同方向,這時過早抽成共用反而會造成麻煩,所以我們還是要根據真實的業務邏輯去思考架構。

程式設計原則三:低到中度扇出、Low-to-medium Fan-out

Code Complete 的定義是:一個模組只依賴少量的其他模組,意思是一個函式直接認識的外部東西越少越好。

如果一個函式同時依賴七個以上的外部服務或模組,通常代表它承擔了太多責任,已經開始變得過於複雜。

以課程頁面同步為例:

ts
export async function syncCoursePage(courseId: string) {
  const course = await db.query.courses.findFirst(...);
  const lessons = await db.query.courseLessons.findMany(...);
  const faq = await db.query.courseFaqItems.findMany(...);
  const comments = await db.query.comments.findMany(...);
  await cache.set(`course:${courseId}`, { course, lessons, faq, comments });
  await searchService.sync(courseId);
  await cdnService.purge(`/courses/${course?.slug}`);
}

這個函式直接碰了資料庫四張表、快取、搜尋服務、CDN,共七個外部依賴。

任何一個服務的介面變動,都可能要回來改這個函式,所以我們可以將快許和發布的細節收到自己的模組當中:

ts
export async function syncCoursePage(courseId: string) {
  const pageData = await coursePageService.load(courseId);
  await coursePageCache.save(courseId, pageData);
  await coursePagePublisher.publish(pageData);
}

上層只看到三個動作,依賴模組從七個降回三個,之後要替換其中一段實作也比較容易處理。

程式設計原則四:良好分層性(Stratification)

白話說就是:每一層只做自己的事,不需要跑去看另一層才能理解現在在做什麼。改畫面時只看畫面層,改業務邏輯時只看 use case 層,每一層都有自己清楚的位置。

以前端 UI 為例:

tsx
export async function CourseCard({ courseId }: { courseId: string }) {
  const course = await db.course.findUnique({
    where: { id: courseId },
    include: { teacher: true },
  });

  return (
    <div>
      <h2>{course?.title}</h2>
      <p>{course?.teacher.name}</p>
    </div>
  );
}

這個 component 同時做了查資料庫和渲染畫面兩件事,閱讀時必須同時理解 Prisma 查詢語法和 JSX 結構,層次混在一起。

tsx
export async function CourseCardContainer({ courseId }: { courseId: string }) {
  const course = await getCourseCardData(courseId);
  return <CourseCard title={course.title} teacherName={course.teacherName} />;
}
tsx
type CourseCardProps = {
  title: string;
  teacherName: string;
};

export function CourseCard({ title, teacherName }: CourseCardProps) {
  return (
    <div>
      <h2>{title}</h2>
      <p>{teacherName}</p>
    </div>
  );
}

改完後,畫面層只負責畫面,資料層只負責資料。

改 UI 時不需要看資料庫查詢,改查詢邏輯時不需要看 JSX 結構。每一層都有自己清楚的職責範圍。

架構層也是同樣道理:route 只負責進出 HTTP,use case 只負責業務流程,repository 只負責資料存取。

層與層之間的邊界越清楚,出問題時才知道該往哪一層找。

程式設計原則五:低耦合(Loose Coupling)

Code Complete 的定義是:把程式各部分之間的連結降到最低。透過良好的介面抽象、封裝和資訊隱藏,讓模組之間的直接相連減少,連結越少,整合、測試、維護的成本就越低。

以訂單建立流程為例:

ts
export async function createOrder(input: CreateOrderInput) {
  const payment = await stripeClient.charge(input.cardToken, input.amount);

  const order = await db
    .insert(orders)
    .values({
      id: nanoid(),
      userId: input.userId,
      amount: input.amount,
      paymentId: payment.id,
    })
    .returning();

  await resend.emails.send({
    to: input.email,
    subject: '訂單成立',
  });

  return order[0];
}

這個函式直接寫死了 Stripe SDK、Drizzle 查詢語法、Resend API,三個外部服務的實作細節全部暴露在主流程裡。

之後只要其中任何一個服務要換,主流程就得跟著改。

ts
export async function createOrder(input: CreateOrderInput) {
  const payment = await paymentGateway.charge({
    token: input.cardToken,
    amount: input.amount,
  });

  const order = await orderRepository.create({
    userId: input.userId,
    amount: input.amount,
    paymentId: payment.id,
  });

  await orderNotifier.sendCreated(order);
  return order;
}

主流程只認識 paymentGateway、orderRepository、orderNotifier 這三個介面,

不在乎背後是 Stripe 還是別家、是 Drizzle 還是其他 ORM。之後要換掉任何一個服務,改動只需要在各自的 adapter 那層,主流程完全不需要動。

低耦合 vs 依賴數量少

低耦合和上一節的依賴數量少看起來有點類似,但我認為他們的切入點不同。依賴數量少是在說這個函式接觸到幾個外部東西?低耦合則次是在談論接觸的方式。

換句話說,你可以只依賴一個服務,但直接綁死它的 SDK,這樣依賴數量少,耦合卻很高。低耦合的關鍵在於透過介面認識對方,而不是直接依賴具體實作。

總結

這五個原則背後其實只有一件事:控制複雜度。

Code Complete 說,軟體開發最核心的技術挑戰就是管理複雜度。

程式難以維護、難以擴充,根本原因通常不是技術選型錯誤,而是複雜度失控,讓人沒辦法在改一個地方的時候忽略其他地方。

而控制複雜度的方法,就是維持良好的軟體開發習慣,尤其是在 AI 大量開發的情況下更要維護好品質。

下一篇看什麼?

01.

2026 新手該如何學習 AI 應用?

02.

六大 AI 典型失敗模式 - 從 AI 時代看軟體工程師的價值

03.

實用的 12 個 Claude Code 技巧 - Vibe Coding 必備!

文章目錄

  1. 程式設計原則一:最小複雜度
  2. 程式設計原則二:高扇入、High Fan-in
  3. 程式設計原則三:低到中度扇出、Low-to-medium Fan-out
  4. 程式設計原則四:良好分層性(Stratification)
  5. 程式設計原則五:低耦合(Loose Coupling)
  6. 總結

訂閱電子報!

和 2000+ 人一起學習 AI、軟體與網站實作資訊。

或來信合作:kun@thisweb.dev

頁面導覽

  • 首頁
  • 所有文章

聯絡資訊

THISWEB