> For the complete documentation index, see [llms.txt](https://docs.ipcheck.ing/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ipcheck.ing/developer/zh-tw/development/coding-conventions.md).

# 編碼慣例

僅限 JavaScript 的規則、函式風格、註解標準，以及後端日誌記錄慣例。

這些是此程式碼庫的規則。它們不是可在每次拉取請求中爭論的風格偏好——它們是審查者檢查的標準，也是讓一個擁有兩個執行環境且語言數量持續增加的專案仍可讀的關鍵。

## 語言

{% hint style="warning" %}
**只限 JavaScript。不使用 TypeScript。**
{% endhint %}

新檔案為 `.js` 或 `.vue`。不使用 `lang="ts"` 在 `<script setup>` 區塊中，也不使用 `.ts` 模組；不進行逐步的 TypeScript 遷移。引入 TypeScript 的 PR 會被要求移除它。

程式碼註解、提交訊息與儲存庫文件都以 **英文**撰寫。區域包自然會使用各自的語言。

## 函式

新函式與重寫的函式使用 **`const` 箭頭語法**:

{% code title="本專案慣用風格" %}

```js
const isValidMAC = (address) => {
    const normalized = address.replace(/[:-]/g, '');
    return normalized.length === 12 && /^[0-9A-Fa-f]+$/.test(normalized);
};

const loadSecurityChecklist = async () => { /* … */ };
```

{% endcode %}

有兩個注意事項：

* **物件方法維持簡寫語法。** `{ status(code) { … } }` 維持原樣。
* **箭頭 const 不會被提升。** 先在呼叫它們的程式碼之前宣告它們。

這適用於你撰寫或重寫的程式碼。 **不要** 大量轉換現有的 `function` 宣告——充滿不相關風格雜訊的 diff，比它所隱藏的功能更難審查。

## 註解

有三條規則，依重要性排序：

1. **每個新檔案都以標頭註解開頭，說明其用途。** 對 API handler 而言，這表示它的路由與用途。這就是整個程式碼庫即使不打開每個檔案也能導覽的方式——目錄層級文件刻意停在「閱讀標頭註解」。
2. **大型模板與大型函式會依有意義的區段附上區塊註解。** 一個 400 行的 `.vue` 模板應該告訴你輸入區域在哪裡結束，以及結果區域在哪裡開始。
3. **註解描述的是當前的程式碼。** 不要敘述變更紀錄：不要寫「我們之前做 X」、不要寫「這修正了……的錯誤」。Git 歷史涵蓋過去。能解釋 *為什麼* 做出某個不明顯選擇的註解很有價值；講述產生它的編輯過程的註解只是噪音。

註解的篇幅應該比它所解釋的程式碼更短。

## 前端慣例

完整架構見 [前端](/developer/zh-tw/architecture/frontend.md)。你在撰寫程式碼時需要遵守的規則：

* **Composition API， `<script setup>`到處都使用。** 不要使用 Options API。
* **路徑別名 `@` → `frontend/`.** 以 `@/utils/valid-ip.js`匯入，絕不要帶著一堆 `../`.
* **先用 shadcn-vue。** 先檢查 `frontend/components/ui/` 是否已有現成原語，再到 shadcn-vue 目錄找可直接複製的版本。手刻 Tailwind 是最後手段。
* **只使用語意化設計 token。** `bg-info`, `bg-action`, `text-muted-foreground`，以及同類項目——這些 token 本身就會主題化。絕不要寫 `dark:` 成對雙套工具類。
* **`console.*` 在前端是可以的。** 它只在後端被禁止（見下文）。

### 跨元件通訊

三種機制，各司其職：

* **事件** (`utils/app-events.js`) 廣播事實——「速度測試完成了」——給任意數量的訂閱者，且沒有回傳值。
* **命令** (`utils/app-commands.js`) 是具結果的命令式觸發——「執行 WebRTC 測試」——由恰好一個元件擁有，依名稱派送，工作完成時解析。
* **模板 ref** 只用於 UI 裝飾——聚焦輸入框、測量元素。絕不要用它讓另一個元件做事。

兩個總線都收錄於 [前端](/developer/zh-tw/architecture/frontend.md).

### 輔助函式該放哪裡

這個決定幾乎在每次變更都會遇到，所以有固定答案：

<table><thead><tr><th width="200">目錄</th><th>應該放在那裡的內容</th></tr></thead><tbody><tr><td><code>frontend/composables/</code></td><td>需要 Vue 響應性或生命週期的邏輯。命名為 <code>use-xxx.js</code>，匯出 <code>useXxx()</code>.</td></tr><tr><td><code>frontend/utils/</code></td><td>與框架無關的輔助函式與 I/O。絕不要用 <code>use-</code> 前綴。</td></tr><tr><td><code>frontend/lib/</code></td><td>僅供 shadcn 支援層使用——目前只有 <code>cn()</code>。不要再加入東西。</td></tr><tr><td><code>frontend/data/</code></td><td>靜態設定與登錄表：工具、區段、成就、變更紀錄。</td></tr></tbody></table>

有一個細化規則： **與 composable 並列存在的純函式，會從該 composable 的檔案匯出**，而不是提升成 `utils/`. `ipFieldTone()` 從 `composables/use-status-tone.js` 開始輸出是應遵循的模式。

## 共享程式碼放在 `common/`

雙方都需要的任何東西都放進 `common/` ——唯一事實來源——，而前端透過一個 **薄型重新匯出橋接層** 於 `utils/`中取得，因此前端匯入可維持熟悉的 `@/utils/...` 形式：

{% code title="frontend/utils/valid-ip.js" %}

```js
// 唯一事實來源是 common/valid-ip.js（與後端共享）。
// 此檔案僅作為薄型重新匯出，因此前端程式碼可以繼續撰寫
// `import { isValidIP } from '@/utils/valid-ip.js'`，而不必在意
// 實作位於何處。
export { isValidIP, isIPv6, isValidDomain, isUsablePublicIP } from '../../common/valid-ip.js';
```

{% endcode %}

`frontend/utils/fetch-with-timeout.js`, `bgp-prefix.js` 與 `ip-math.js` 遵循相同模式（最後一個則直接用 `export *`，因為整個模組都可安全在瀏覽器中執行）。新增橋接時，也新增一個規格，匯入 **兩條** 路徑並斷言它們一致—— `tests/valid-ip.test.js` 正是如此，而 `tests/ip-math.test.js` 會在單一迴圈中檢查模組的每個匯出——這就是避免橋接悄悄長出第二份實作的關鍵。

{% hint style="info" %}
**`isValidIP` 與 `isUsablePublicIP` 回答的是不同問題。** `isValidIP` 它會判斷字串是否為格式正確的位址，並且在該位址只是被讀取或顯示時，這始終是正確的選擇——解析 MTR hop 輸出、過濾報告欄位、為遙測遮蔽 IP、判斷是否顯示複製按鈕。

`isUsablePublicIP` 它會判斷一個位址是否值得送去外部查詢：先納入有效性，再排除保留區段（RFC 1918、loopback、CGNAT、link-local、文件用途、多播，以及它們對應的 IPv6 版本）。只有在該值即將成為對登錄、地理位置來源或探測機群的查詢時才使用它——這些都無法對私人位址說出任何有意義的資訊。其他所有情況下， `isValidIP` 仍然是你想要的。

這兩者都不會給你數值。當你需要 *計算* 一個位址的用途時——遮罩、比較、測試 CIDR 包含、分割或聚合區塊——那就是 `common/ip-math.js` (`parseIp` → `{ family, value: bigint }`, `parseCidr`, `cidrContains`，以及其他相關函式）。其解析器刻意比 `isValidIP`：像 `01.2.3.4` 這樣帶前導零的八位元組會被拒絕，而不是當成八進位讀取，且輔助函式會回傳 `null` 而不是拋出例外。
{% endhint %}

## 後端慣例

完整圖像見 [後端](/developer/zh-tw/architecture/backend.md)。以下是要點規則：

### Handler 形狀

每個路由一個檔案，放在 `api/`底下，並且只有單一預設匯出：

```js
export default async (req, res) => {
    // 讀取 req.query / req.body，呼叫上游，寫出單一回應
};
```

錯誤格式簡潔且一致： `400` 輸入有誤時， `res.status(500).json({ error: error.message })` 上游失敗時。前端不會原樣顯示這些內容。

### 絕不使用裸露的 `fetch()`

每一次對外 HTTP 呼叫自 `api/` 都會經由 `fetchUpstream` 來自 `common/fetch-with-timeout.js`。它會套用 8 秒逾時與預設的 `User-Agent`。若上游供應者卡住，必須逾時，而不是讓連線一直開著。

### 守衛，而非內嵌檢查

存取控制與參數驗證都在中介軟體（`common/guards.js`）中，並附加於 `backend-server.js`。Handler 絕不重複它們：

* `requireReferer` ——全域套用於 `/api/*`
* `requirePublicIP()` / `requireValidDomain()` / `requireValidPrefix()` / `requireValidASN()` / `requireValidProviderId()` / `requireValidRecordType()` / `requireValidReportId()` ——每個路由各自套用

新的參數形狀意味著要在 `common/guards.js`中新增一個守衛，而不是在 handler 頂部寫死檢查。

### 記錄

{% hint style="danger" %}
**`console.*` 在後端檔案中是被禁止的。** 永遠使用來自 `common/logger.js`.
{% endhint %}

的共用 pino logger。Pino 是先內容後訊息——物件先、簡短訊息後：

{% code title="本專案慣用風格" %}

```js
import logger from '../common/logger.js';

logger.error({ err: e, mac: macAddress }, 'mac-checker handler failed');
logger.warn({ err: e, query }, 'whois: RDAP IP lookup failed, trying WHOIS');
```

{% endcode %}

還有兩條規則：

* **Handlers 絕不會記錄「已接收請求」這類行。** 每個請求的記錄是 `pino-http`的工作，只在 `/api` 時掛載 `LOG_HTTP=true`.
* **僅在啟動時的行會以表情符號開頭** ——🚀 監聽、📦 就緒、📥 下載、🛡️ 安全、🐢 節流、🗓️ 排程、⚠️ 可復原、❌ 失敗。每個請求的記錄保持純文字。

設定為 `LOG_LEVEL` （預設 `info`), `LOG_FORMAT=json` 供日誌轉送器使用，並且 `LOG_HTTP=true`。此專案中任何地方都沒有 `NODE_ENV` ——見 [記錄](/developer/zh-tw/configuration/logging.md).

### 邊緣快取

每個 `/api/*` 回應預設為 `Cache-Control: no-store`。變動緩慢的公開路由可透過 `cacheable(maxAgeSeconds)` 中介軟體選擇加入，位於 `backend-server.js`中。TTL 要寫成乘法表達式（`24 * 60 * 60`），不要寫原始秒數。Handler 本身絕不碰 `Cache-Control`，而且每使用者或需驗證的端點絕不包裝。

## 隨你的變更一同交付的內容

有兩件事不是可選項，而且都會被強制執行：

* **i18n 覆蓋。** 任何會顯示文案的內容，都要在 **每個 `完整` 語系** 中於同一次變更送出——各語系的 `common/locale-registry.js` 會標記 `完整`；該檔案就是目前的清單——包括 `frontend/data/changelog.json` 條目。標記為 `beta` 的語系可豁免；它們會回退到英文。見 [i18n](/developer/zh-tw/development/i18n.md).
* **測試。** 任何可在不呼叫網路的情況下執行的非視覺邏輯，都要在 `tests/`中隨同規格一起交付，且在同一次變更中完成。當行為改變時，更新受影響的規格——不要延後。見 [測試](/developer/zh-tw/development/testing.md).

然後執行 `pnpm check`。在交接變更前，它必須是綠燈。

## 下一步

* [新增工具](/developer/zh-tw/development/adding-a-new-tool.md) ——以上全部，端到端套用。
* [如何貢獻](/developer/zh-tw/contributing/how-to-contribute.md) ——分支、提交與 PR 預期。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.ipcheck.ing/developer/zh-tw/development/coding-conventions.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
