claude-code/claude-zh/skills/coding-standards/SKILL.md

530 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: coding-standards
description: 通用程式碼標準、最佳實踐與模式,涵蓋 TypeScript、JavaScript、React 與 Node.js 開發。
---
# 程式碼標準與最佳實踐 (Coding Standards & Best Practices)
適用於所有專案的通用程式碼標準。
## 何時啟用
- 開始一個新的專案或模組
- 進行程式碼審查以確保品質與可維護性
- 根據規範重構現有程式碼
- 強制執行命名、格式或結構的一致性
- 設置 Linting、格式化或型別檢查規則
- 指導新貢獻者了解程式碼慣例
## 程式碼品質原則
### 1. 可讀性優先 (Readability First)
- 程式碼被閱讀的次數遠多於撰寫。
- 明確的變數與函式命名。
- 優先使用自解釋程式碼 (Self-documenting code) 而非註解。
- 保持格式一致。
### 2. KISS (Keep It Simple, Stupid) 原則
- 使用最簡單可行的解決方案。
- 避免過度工程 (Over-engineering)。
- 不進行過早的優化 (Premature optimization)。
- 容易理解優於巧妙的程式碼。
### 3. DRY (Don't Repeat Yourself) 原則
- 將共用邏輯擷取到函式中。
- 建立可重用的元件。
- 跨模組共享工具函式。
- 避免「複製貼上」式的程式開發。
### 4. YAGNI (You Aren't Gonna Need It) 原則
- 在需求出現之前不要建立該功能。
- 避免基於預測的通用化設計。
- 僅在必要時增加複雜度。
- 從簡單開始,必要時再進行重構。
## TypeScript/JavaScript 標準
### 變數命名
```typescript
// ✅ 推薦 (GOOD):具備描述性的名稱
const marketSearchQuery = 'election'
const isUserAuthenticated = true
const totalRevenue = 1000
// ❌ 錯誤 (BAD):名稱模糊不清
const q = 'election'
const flag = true
const x = 1000
```
### 函式命名
```typescript
// ✅ 推薦 (GOOD):「動詞-名詞」模式
async function fetchMarketData(marketId: string) { }
function calculateSimilarity(a: number[], b: number[]) { }
function isValidEmail(email: string): boolean { }
// ❌ 錯誤 (BAD):命名不明確或僅包含名詞
async function market(id: string) { }
function similarity(a, b) { }
function email(e) { }
```
### 不可變性模式 (Immutability Pattern - 關鍵)
```typescript
// ✅ 始終使用展開運算子 (Spread operator)
const updatedUser = {
...user,
name: 'New Name'
}
const updatedArray = [...items, newItem]
// ❌ 絕對不要直接修改
user.name = 'New Name' // 錯誤 (BAD)
items.push(newItem) // 錯誤 (BAD)
```
### 錯誤處理
```typescript
// ✅ 推薦 (GOOD):全面的錯誤處理
async function fetchData(url: string) {
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
return await response.json()
} catch (error) {
console.error('Fetch failed:', error)
throw new Error('Failed to fetch data')
}
}
// ❌ 錯誤 (BAD):無錯誤處理
async function fetchData(url) {
const response = await fetch(url)
return response.json()
}
```
### Async/Await 最佳實踐
```typescript
// ✅ 推薦 (GOOD):在可能的情況下執行平行處理
const [users, markets, stats] = await Promise.all([
fetchUsers(),
fetchMarkets(),
fetchStats()
])
// ❌ 錯誤 (BAD):在沒必要時使用循序執行
const users = await fetchUsers()
const markets = await fetchMarkets()
const stats = await fetchStats()
```
### 型別安全 (Type Safety)
```typescript
// ✅ 推薦 (GOOD):使用正確的型別
interface Market {
id: string
name: string
status: 'active' | 'resolved' | 'closed'
created_at: Date
}
function getMarket(id: string): Promise<Market> {
// 實作
}
// ❌ 錯誤 (BAD):使用 'any'
function getMarket(id: any): Promise<any> {
// 實作
}
```
## React 最佳實踐
### 元件結構
```typescript
// ✅ 推薦 (GOOD):帶有型別的函式元件 (Functional component)
interface ButtonProps {
children: React.ReactNode
onClick: () => void
disabled?: boolean
variant?: 'primary' | 'secondary'
}
export function Button({
children,
onClick,
disabled = false,
variant = 'primary'
}: ButtonProps) {
return (
<button
onClick={onClick}
disabled={disabled}
className={`btn btn-${variant}`}
>
{children}
</button>
)
}
// ❌ 錯誤 (BAD):無型別,結構不明確
export function Button(props) {
return <button onClick={props.onClick}>{props.children}</button>
}
```
### 自定義 Hooks (Custom Hooks)
```typescript
// ✅ 推薦 (GOOD):可重用的自定義 Hook
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value)
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value)
}, delay)
return () => clearTimeout(handler)
}, [value, delay])
return debouncedValue
}
// 使用方式
const debouncedQuery = useDebounce(searchQuery, 500)
```
### 狀態管理 (State Management)
```typescript
// ✅ 推薦 (GOOD):正確更新狀態
const [count, setCount] = useState(0)
// 根據前一個狀態使用函式更新
setCount(prev => prev + 1)
// ❌ 錯誤 (BAD):直接參照變數更新狀態
setCount(count + 1) // 在非同步情境下可能會獲取到過時的資料
```
### 條件渲染 (Conditional Rendering)
```typescript
// ✅ 推薦 (GOOD):簡潔的條件渲染
{isLoading && <Spinner />}
{error && <ErrorMessage error={error} />}
{data && <DataDisplay data={data} />}
// ❌ 錯誤 (BAD):三元運算子地獄
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
```
## API 設計標準
### REST API 慣例
```
GET /api/markets # 列出所有市場
GET /api/markets/:id # 獲取特定市場
POST /api/markets # 建立新市場
PUT /api/markets/:id # 更新市場 (完整覆蓋)
PATCH /api/markets/:id # 更新市場 (部分變更)
DELETE /api/markets/:id # 刪除市場
# 用於篩選的查詢參數
GET /api/markets?status=active&limit=10&offset=0
```
### 回應格式
```typescript
// ✅ 推薦 (GOOD):一致的回應結構
interface ApiResponse<T> {
success: boolean
data?: T
error?: string
meta?: {
total: number
page: number
limit: number
}
}
// 成功回應
return NextResponse.json({
success: true,
data: markets,
meta: { total: 100, page: 1, limit: 10 }
})
// 錯誤回應
return NextResponse.json({
success: false,
error: 'Invalid request'
}, { status: 400 })
```
### 輸入驗證
```typescript
import { z } from 'zod'
// ✅ 推薦 (GOOD):使用 Schema 驗證
const CreateMarketSchema = z.object({
name: z.string().min(1).max(200),
description: z.string().min(1).max(2000),
endDate: z.string().datetime(),
categories: z.array(z.string()).min(1)
})
export async function POST(request: Request) {
const body = await request.json()
try {
const validated = CreateMarketSchema.parse(body)
// 使用驗證後的資料繼續處理
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({
success: false,
error: 'Validation failed',
details: error.errors
}, { status: 400 })
}
}
}
```
## 檔案組織
### 專案結構
```
src/
├── app/ # Next.js App Router
│ ├── api/ # API 路由
│ ├── markets/ # 市場相關頁面
│ └── (auth)/ # 身分驗證頁面 (路由分群)
├── components/ # React 元件
│ ├── ui/ # 通用 UI 元件
│ ├── forms/ # 表單元件
│ └── layouts/ # 佈局元件
├── hooks/ # 自定義 React hooks
├── lib/ # 工具函式與配置
│ ├── api/ # API 用戶端
│ ├── utils/ # 助手函式
│ └── constants/ # 常數定義
├── types/ # TypeScript 型別定義
└── styles/ # 全域樣式
```
### 檔案命名
```
components/Button.tsx # 元件使用 PascalCase
hooks/useAuth.ts # 以 'use' 為前綴的 camelCase
lib/formatDate.ts # 工具函式使用 camelCase
types/market.types.ts # 以 .types 為後綴的 camelCase
```
## 註解與文件
### 何時該寫註解
```typescript
// ✅ 推薦 (GOOD):解釋「為什麼 (WHY)」,而不是解釋「做了什麼 (WHAT)」
// 在發生停機時使用指數退避,避免請求淹沒 API
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
// 為了處理大型陣列的效能,此處刻意使用 Mutation 操作
items.push(newItem)
// ❌ 錯誤 (BAD):解釋顯而易見的事情
// 計數器加 1
count++
// 將名稱設為使用者的名稱
name = user.name
```
### 對外 API 使用 JSDoc
```typescript
/**
* 使用語義相似度搜尋市場。
*
* @param query - 自然語言搜尋查詢
* @param limit - 最大結果數量 (預設為 10)
* @returns 根據相似度分數排序的市場陣列
* @throws {Error} 若 OpenAI API 失敗或 Redis 不可用時
*
* @example
* ```typescript
* const results = await searchMarkets('election', 5)
* console.log(results[0].name) // "Trump vs Biden"
* ```
*/
export async function searchMarkets(
query: string,
limit: number = 10
): Promise<Market[]> {
// 實作
}
```
## 效能最佳實踐
### 記憶化 (Memoization)
```typescript
import { useMemo, useCallback } from 'react'
// ✅ 推薦 (GOOD):記憶化高成本的運算
const sortedMarkets = useMemo(() => {
return markets.sort((a, b) => b.volume - a.volume)
}, [markets])
// ✅ 推薦 (GOOD):記憶化回呼函式 (Callbacks)
const handleSearch = useCallback((query: string) => {
setSearchQuery(query)
}, [])
```
### 延遲載入 (Lazy Loading)
```typescript
import { lazy, Suspense } from 'react'
// ✅ 推薦 (GOOD):延遲載入重量級元件
const HeavyChart = lazy(() => import('./HeavyChart'))
export function Dashboard() {
return (
<Suspense fallback={<Spinner />}>
<HeavyChart />
</Suspense>
)
}
```
### 資料庫查詢
```typescript
// ✅ 推薦 (GOOD):僅選取所需欄位
const { data } = await supabase
.from('markets')
.select('id, name, status')
.limit(10)
// ❌ 錯誤 (BAD):選取所有欄位
const { data } = await supabase
.from('markets')
.select('*')
```
## 測試標準
### 測試結構 (AAA 模式)
```typescript
test('準確計算相似度', () => {
// 排列 (Arrange)
const vector1 = [1, 0, 0]
const vector2 = [0, 1, 0]
// 動作 (Act)
const similarity = calculateCosineSimilarity(vector1, vector2)
// 斷言 (Assert)
expect(similarity).toBe(0)
})
```
### 測試命名
```typescript
// ✅ 推薦 (GOOD):具備描述性的測試名稱
test('當沒有市場匹配查詢時應返回空陣列', () => { })
test('當缺失 OpenAI API key 時應抛出錯誤', () => { })
test('當 Redis 不可用時應回退到子字串搜尋', () => { })
// ❌ 錯誤 (BAD):測試名稱模糊
test('works', () => { })
test('test search', () => { })
```
## 程式碼異味 (Code Smell) 偵測
留意以下反模式 (Anti-patterns)
### 1. 過長的函式
```typescript
// ❌ 錯誤 (BAD):函式超過 50 行
function processMarketData() {
// 100 行程式碼
}
// ✅ 推薦 (GOOD):拆分為更小的函式
function processMarketData() {
const validated = validateData()
const transformed = transformData(validated)
return saveData(transformed)
}
```
### 2. 過深的巢狀層級
```typescript
// ❌ 錯誤 (BAD)5 層以上的巢狀
if (user) {
if (user.isAdmin) {
if (market) {
if (market.isActive) {
if (hasPermission) {
// 執行某些操作
}
}
}
}
}
// ✅ 推薦 (GOOD):快速返回 (Early returns)
if (!user) return
if (!user.isAdmin) return
if (!market) return
if (!market.isActive) return
if (!hasPermission) return
// 執行某些操作
```
### 3. 魔術數字 (Magic Numbers)
```typescript
// ❌ 錯誤 (BAD):未加說明的數字
if (retryCount > 3) { }
setTimeout(callback, 500)
// ✅ 推薦 (GOOD):命名常數
const MAX_RETRIES = 3
const DEBOUNCE_DELAY_MS = 500
if (retryCount > MAX_RETRIES) { }
setTimeout(callback, DEBOUNCE_DELAY_MS)
```
**請記住**:程式碼品質是不容妥協的。清晰、可維護的程式碼能實現快速開發並讓重構更具信心。