mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
fix: harden unicode safety checks
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌐 Language / 语言 / 語言**
|
||||
**Language / 语言 / 語言**
|
||||
|
||||
[**English**](../../README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](README.md) | [日本語](../../docs/ja-JP/README.md) | [한국어](../ko-KR/README.md)
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速開始
|
||||
## 快速開始
|
||||
|
||||
在 2 分鐘內快速上手:
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
### 第二步:安裝規則(必需)
|
||||
|
||||
> ⚠️ **重要提示:** Claude Code 外掛程式無法自動分發 `rules`,需要手動安裝:
|
||||
> WARNING: **重要提示:** Claude Code 外掛程式無法自動分發 `rules`,需要手動安裝:
|
||||
|
||||
```bash
|
||||
# 首先複製儲存庫
|
||||
@@ -98,11 +98,11 @@ cp -r everything-claude-code/rules/* ~/.claude/rules/
|
||||
/plugin list everything-claude-code@everything-claude-code
|
||||
```
|
||||
|
||||
✨ **完成!** 您現在使用 15+ 代理程式、30+ 技能和 20+ 指令。
|
||||
**完成!** 您現在使用 15+ 代理程式、30+ 技能和 20+ 指令。
|
||||
|
||||
---
|
||||
|
||||
## 🌐 跨平台支援
|
||||
## 跨平台支援
|
||||
|
||||
此外掛程式現已完整支援 **Windows、macOS 和 Linux**。所有鉤子和腳本已使用 Node.js 重寫以獲得最佳相容性。
|
||||
|
||||
@@ -137,7 +137,7 @@ node scripts/setup-package-manager.js --detect
|
||||
|
||||
---
|
||||
|
||||
## 📦 內容概覽
|
||||
## 內容概覽
|
||||
|
||||
本儲存庫是一個 **Claude Code 外掛程式** - 可直接安裝或手動複製元件。
|
||||
|
||||
@@ -237,7 +237,7 @@ everything-claude-code/
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 生態系統工具
|
||||
## 生態系統工具
|
||||
|
||||
### ecc.tools - 技能建立器
|
||||
|
||||
@@ -259,7 +259,7 @@ everything-claude-code/
|
||||
|
||||
---
|
||||
|
||||
## 📥 安裝
|
||||
## 安裝
|
||||
|
||||
### 選項 1:以外掛程式安裝(建議)
|
||||
|
||||
@@ -295,7 +295,7 @@ everything-claude-code/
|
||||
|
||||
---
|
||||
|
||||
### 🔧 選項 2:手動安裝
|
||||
### 選項 2:手動安裝
|
||||
|
||||
如果您偏好手動控制安裝內容:
|
||||
|
||||
@@ -328,7 +328,7 @@ cp -r everything-claude-code/skills/* ~/.claude/skills/
|
||||
|
||||
---
|
||||
|
||||
## 🎯 核心概念
|
||||
## 核心概念
|
||||
|
||||
### 代理程式(Agents)
|
||||
|
||||
@@ -386,7 +386,7 @@ You are a senior code reviewer...
|
||||
|
||||
---
|
||||
|
||||
## 🧪 執行測試
|
||||
## 執行測試
|
||||
|
||||
外掛程式包含完整的測試套件:
|
||||
|
||||
@@ -402,7 +402,7 @@ node tests/hooks/hooks.test.js
|
||||
|
||||
---
|
||||
|
||||
## 🤝 貢獻
|
||||
## 貢獻
|
||||
|
||||
**歡迎並鼓勵貢獻。**
|
||||
|
||||
@@ -424,7 +424,7 @@ node tests/hooks/hooks.test.js
|
||||
|
||||
---
|
||||
|
||||
## 📖 背景
|
||||
## 背景
|
||||
|
||||
我從實驗性推出就開始使用 Claude Code。2025 年 9 月與 [@DRodriguezFX](https://x.com/DRodriguezFX) 一起使用 Claude Code 打造 [zenith.chat](https://zenith.chat),贏得了 Anthropic x Forum Ventures 黑客松。
|
||||
|
||||
@@ -432,7 +432,7 @@ node tests/hooks/hooks.test.js
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 重要注意事項
|
||||
## WARNING: 重要注意事項
|
||||
|
||||
### 上下文視窗管理
|
||||
|
||||
@@ -455,13 +455,13 @@ node tests/hooks/hooks.test.js
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Star 歷史
|
||||
## Star 歷史
|
||||
|
||||
[](https://star-history.com/#affaan-m/everything-claude-code&Date)
|
||||
|
||||
---
|
||||
|
||||
## 🔗 連結
|
||||
## 連結
|
||||
|
||||
- **簡明指南(從這裡開始):** [Everything Claude Code 簡明指南](https://x.com/affaanmustafa/status/2012378465664745795)
|
||||
- **完整指南(進階):** [Everything Claude Code 完整指南](https://x.com/affaanmustafa/status/2014040193557471352)
|
||||
@@ -471,7 +471,7 @@ node tests/hooks/hooks.test.js
|
||||
|
||||
---
|
||||
|
||||
## 📄 授權
|
||||
## 授權
|
||||
|
||||
MIT - 自由使用、依需求修改、如可能請回饋貢獻。
|
||||
|
||||
|
||||
@@ -101,12 +101,12 @@ c) 依影響排序優先順序
|
||||
|
||||
**模式 1:型別推論失敗**
|
||||
```typescript
|
||||
// ❌ 錯誤:Parameter 'x' implicitly has an 'any' type
|
||||
// FAIL: 錯誤:Parameter 'x' implicitly has an 'any' type
|
||||
function add(x, y) {
|
||||
return x + y
|
||||
}
|
||||
|
||||
// ✅ 修復:新增型別註解
|
||||
// PASS: 修復:新增型別註解
|
||||
function add(x: number, y: number): number {
|
||||
return x + y
|
||||
}
|
||||
@@ -114,25 +114,25 @@ function add(x: number, y: number): number {
|
||||
|
||||
**模式 2:Null/Undefined 錯誤**
|
||||
```typescript
|
||||
// ❌ 錯誤:Object is possibly 'undefined'
|
||||
// FAIL: 錯誤:Object is possibly 'undefined'
|
||||
const name = user.name.toUpperCase()
|
||||
|
||||
// ✅ 修復:可選串聯
|
||||
// PASS: 修復:可選串聯
|
||||
const name = user?.name?.toUpperCase()
|
||||
|
||||
// ✅ 或:Null 檢查
|
||||
// PASS: 或:Null 檢查
|
||||
const name = user && user.name ? user.name.toUpperCase() : ''
|
||||
```
|
||||
|
||||
**模式 3:缺少屬性**
|
||||
```typescript
|
||||
// ❌ 錯誤:Property 'age' does not exist on type 'User'
|
||||
// FAIL: 錯誤:Property 'age' does not exist on type 'User'
|
||||
interface User {
|
||||
name: string
|
||||
}
|
||||
const user: User = { name: 'John', age: 30 }
|
||||
|
||||
// ✅ 修復:新增屬性到介面
|
||||
// PASS: 修復:新增屬性到介面
|
||||
interface User {
|
||||
name: string
|
||||
age?: number // 如果不是總是存在則為可選
|
||||
@@ -141,10 +141,10 @@ interface User {
|
||||
|
||||
**模式 4:Import 錯誤**
|
||||
```typescript
|
||||
// ❌ 錯誤:Cannot find module '@/lib/utils'
|
||||
// FAIL: 錯誤:Cannot find module '@/lib/utils'
|
||||
import { formatDate } from '@/lib/utils'
|
||||
|
||||
// ✅ 修復 1:檢查 tsconfig paths 是否正確
|
||||
// PASS: 修復 1:檢查 tsconfig paths 是否正確
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
@@ -153,22 +153,22 @@ import { formatDate } from '@/lib/utils'
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 修復 2:使用相對 import
|
||||
// PASS: 修復 2:使用相對 import
|
||||
import { formatDate } from '../lib/utils'
|
||||
|
||||
// ✅ 修復 3:安裝缺少的套件
|
||||
// PASS: 修復 3:安裝缺少的套件
|
||||
npm install @/lib/utils
|
||||
```
|
||||
|
||||
**模式 5:型別不符**
|
||||
```typescript
|
||||
// ❌ 錯誤:Type 'string' is not assignable to type 'number'
|
||||
// FAIL: 錯誤:Type 'string' is not assignable to type 'number'
|
||||
const age: number = "30"
|
||||
|
||||
// ✅ 修復:解析字串為數字
|
||||
// PASS: 修復:解析字串為數字
|
||||
const age: number = parseInt("30", 10)
|
||||
|
||||
// ✅ 或:變更型別
|
||||
// PASS: 或:變更型別
|
||||
const age: string = "30"
|
||||
```
|
||||
|
||||
@@ -177,34 +177,34 @@ const age: string = "30"
|
||||
**關鍵:做最小可能的變更**
|
||||
|
||||
### 應該做:
|
||||
✅ 在缺少處新增型別註解
|
||||
✅ 在需要處新增 null 檢查
|
||||
✅ 修復 imports/exports
|
||||
✅ 新增缺少的相依性
|
||||
✅ 更新型別定義
|
||||
✅ 修復設定檔
|
||||
PASS: 在缺少處新增型別註解
|
||||
PASS: 在需要處新增 null 檢查
|
||||
PASS: 修復 imports/exports
|
||||
PASS: 新增缺少的相依性
|
||||
PASS: 更新型別定義
|
||||
PASS: 修復設定檔
|
||||
|
||||
### 不應該做:
|
||||
❌ 重構不相關的程式碼
|
||||
❌ 變更架構
|
||||
❌ 重新命名變數/函式(除非是錯誤原因)
|
||||
❌ 新增功能
|
||||
❌ 變更邏輯流程(除非是修復錯誤)
|
||||
❌ 優化效能
|
||||
❌ 改善程式碼風格
|
||||
FAIL: 重構不相關的程式碼
|
||||
FAIL: 變更架構
|
||||
FAIL: 重新命名變數/函式(除非是錯誤原因)
|
||||
FAIL: 新增功能
|
||||
FAIL: 變更邏輯流程(除非是修復錯誤)
|
||||
FAIL: 優化效能
|
||||
FAIL: 改善程式碼風格
|
||||
|
||||
**最小差異範例:**
|
||||
|
||||
```typescript
|
||||
// 檔案有 200 行,第 45 行有錯誤
|
||||
|
||||
// ❌ 錯誤:重構整個檔案
|
||||
// FAIL: 錯誤:重構整個檔案
|
||||
// - 重新命名變數
|
||||
// - 抽取函式
|
||||
// - 變更模式
|
||||
// 結果:50 行變更
|
||||
|
||||
// ✅ 正確:只修復錯誤
|
||||
// PASS: 正確:只修復錯誤
|
||||
// - 在第 45 行新增型別註解
|
||||
// 結果:1 行變更
|
||||
|
||||
@@ -212,12 +212,12 @@ function processData(data) { // 第 45 行 - 錯誤:'data' implicitly has 'any
|
||||
return data.map(item => item.value)
|
||||
}
|
||||
|
||||
// ✅ 最小修復:
|
||||
// PASS: 最小修復:
|
||||
function processData(data: any[]) { // 只變更這行
|
||||
return data.map(item => item.value)
|
||||
}
|
||||
|
||||
// ✅ 更好的最小修復(如果知道型別):
|
||||
// PASS: 更好的最小修復(如果知道型別):
|
||||
function processData(data: Array<{ value: number }>) {
|
||||
return data.map(item => item.value)
|
||||
}
|
||||
@@ -232,7 +232,7 @@ function processData(data: Array<{ value: number }>) {
|
||||
**建置目標:** Next.js 生產 / TypeScript 檢查 / ESLint
|
||||
**初始錯誤:** X
|
||||
**已修復錯誤:** Y
|
||||
**建置狀態:** ✅ 通過 / ❌ 失敗
|
||||
**建置狀態:** PASS: 通過 / FAIL: 失敗
|
||||
|
||||
## 已修復的錯誤
|
||||
|
||||
@@ -260,11 +260,11 @@ Parameter 'market' implicitly has an 'any' type.
|
||||
|
||||
## 驗證步驟
|
||||
|
||||
1. ✅ TypeScript 檢查通過:`npx tsc --noEmit`
|
||||
2. ✅ Next.js 建置成功:`npm run build`
|
||||
3. ✅ ESLint 檢查通過:`npx eslint .`
|
||||
4. ✅ 沒有引入新錯誤
|
||||
5. ✅ 開發伺服器執行:`npm run dev`
|
||||
1. PASS: TypeScript 檢查通過:`npx tsc --noEmit`
|
||||
2. PASS: Next.js 建置成功:`npm run build`
|
||||
3. PASS: ESLint 檢查通過:`npx eslint .`
|
||||
4. PASS: 沒有引入新錯誤
|
||||
5. PASS: 開發伺服器執行:`npm run dev`
|
||||
```
|
||||
|
||||
## 何時使用此 Agent
|
||||
@@ -287,13 +287,13 @@ Parameter 'market' implicitly has an 'any' type.
|
||||
## 成功指標
|
||||
|
||||
建置錯誤解決後:
|
||||
- ✅ `npx tsc --noEmit` 以代碼 0 結束
|
||||
- ✅ `npm run build` 成功完成
|
||||
- ✅ 沒有引入新錯誤
|
||||
- ✅ 變更行數最小(< 受影響檔案的 5%)
|
||||
- ✅ 建置時間沒有顯著增加
|
||||
- ✅ 開發伺服器無錯誤執行
|
||||
- ✅ 測試仍然通過
|
||||
- PASS: `npx tsc --noEmit` 以代碼 0 結束
|
||||
- PASS: `npm run build` 成功完成
|
||||
- PASS: 沒有引入新錯誤
|
||||
- PASS: 變更行數最小(< 受影響檔案的 5%)
|
||||
- PASS: 建置時間沒有顯著增加
|
||||
- PASS: 開發伺服器無錯誤執行
|
||||
- PASS: 測試仍然通過
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -81,15 +81,15 @@ model: opus
|
||||
問題:API 金鑰暴露在原始碼中
|
||||
修復:移至環境變數
|
||||
|
||||
const apiKey = "sk-abc123"; // ❌ 錯誤
|
||||
const apiKey = "sk-abc123"; // FAIL: 錯誤
|
||||
const apiKey = process.env.API_KEY; // ✓ 正確
|
||||
```
|
||||
|
||||
## 批准標準
|
||||
|
||||
- ✅ 批准:無關鍵或高優先問題
|
||||
- ⚠️ 警告:僅有中優先問題(可謹慎合併)
|
||||
- ❌ 阻擋:發現關鍵或高優先問題
|
||||
- PASS: 批准:無關鍵或高優先問題
|
||||
- WARNING: 警告:僅有中優先問題(可謹慎合併)
|
||||
- FAIL: 阻擋:發現關鍵或高優先問題
|
||||
|
||||
## 專案特定指南(範例)
|
||||
|
||||
|
||||
@@ -109,14 +109,14 @@ c) 資料保護
|
||||
**影響:** 大表上查詢快 100-1000 倍
|
||||
|
||||
```sql
|
||||
-- ❌ 錯誤:外鍵沒有索引
|
||||
-- FAIL: 錯誤:外鍵沒有索引
|
||||
CREATE TABLE orders (
|
||||
id bigint PRIMARY KEY,
|
||||
customer_id bigint REFERENCES customers(id)
|
||||
-- 缺少索引!
|
||||
);
|
||||
|
||||
-- ✅ 正確:外鍵有索引
|
||||
-- PASS: 正確:外鍵有索引
|
||||
CREATE TABLE orders (
|
||||
id bigint PRIMARY KEY,
|
||||
customer_id bigint REFERENCES customers(id)
|
||||
@@ -134,11 +134,11 @@ CREATE INDEX orders_customer_id_idx ON orders (customer_id);
|
||||
| **Hash** | 僅等於 | `=`(比 B-tree 略快)|
|
||||
|
||||
```sql
|
||||
-- ❌ 錯誤:JSONB 包含用 B-tree
|
||||
-- FAIL: 錯誤:JSONB 包含用 B-tree
|
||||
CREATE INDEX products_attrs_idx ON products (attributes);
|
||||
SELECT * FROM products WHERE attributes @> '{"color": "red"}';
|
||||
|
||||
-- ✅ 正確:JSONB 用 GIN
|
||||
-- PASS: 正確:JSONB 用 GIN
|
||||
CREATE INDEX products_attrs_idx ON products USING gin (attributes);
|
||||
```
|
||||
|
||||
@@ -147,11 +147,11 @@ CREATE INDEX products_attrs_idx ON products USING gin (attributes);
|
||||
**影響:** 多欄位查詢快 5-10 倍
|
||||
|
||||
```sql
|
||||
-- ❌ 錯誤:分開的索引
|
||||
-- FAIL: 錯誤:分開的索引
|
||||
CREATE INDEX orders_status_idx ON orders (status);
|
||||
CREATE INDEX orders_created_idx ON orders (created_at);
|
||||
|
||||
-- ✅ 正確:複合索引(等於欄位在前,然後範圍)
|
||||
-- PASS: 正確:複合索引(等於欄位在前,然後範圍)
|
||||
CREATE INDEX orders_status_created_idx ON orders (status, created_at);
|
||||
```
|
||||
|
||||
@@ -167,11 +167,11 @@ CREATE INDEX orders_status_created_idx ON orders (status, created_at);
|
||||
**影響:** 透過避免表查找,查詢快 2-5 倍
|
||||
|
||||
```sql
|
||||
-- ❌ 錯誤:必須從表獲取 name
|
||||
-- FAIL: 錯誤:必須從表獲取 name
|
||||
CREATE INDEX users_email_idx ON users (email);
|
||||
SELECT email, name FROM users WHERE email = 'user@example.com';
|
||||
|
||||
-- ✅ 正確:所有欄位在索引中
|
||||
-- PASS: 正確:所有欄位在索引中
|
||||
CREATE INDEX users_email_idx ON users (email) INCLUDE (name, created_at);
|
||||
```
|
||||
|
||||
@@ -180,10 +180,10 @@ CREATE INDEX users_email_idx ON users (email) INCLUDE (name, created_at);
|
||||
**影響:** 索引小 5-20 倍,寫入和查詢更快
|
||||
|
||||
```sql
|
||||
-- ❌ 錯誤:完整索引包含已刪除的列
|
||||
-- FAIL: 錯誤:完整索引包含已刪除的列
|
||||
CREATE INDEX users_email_idx ON users (email);
|
||||
|
||||
-- ✅ 正確:部分索引排除已刪除的列
|
||||
-- PASS: 正確:部分索引排除已刪除的列
|
||||
CREATE INDEX users_active_email_idx ON users (email) WHERE deleted_at IS NULL;
|
||||
```
|
||||
|
||||
@@ -196,11 +196,11 @@ CREATE INDEX users_active_email_idx ON users (email) WHERE deleted_at IS NULL;
|
||||
**影響:** 關鍵 - 資料庫強制的租戶隔離
|
||||
|
||||
```sql
|
||||
-- ❌ 錯誤:僅應用程式篩選
|
||||
-- FAIL: 錯誤:僅應用程式篩選
|
||||
SELECT * FROM orders WHERE user_id = $current_user_id;
|
||||
-- Bug 意味著所有訂單暴露!
|
||||
|
||||
-- ✅ 正確:資料庫強制的 RLS
|
||||
-- PASS: 正確:資料庫強制的 RLS
|
||||
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
|
||||
|
||||
@@ -220,11 +220,11 @@ CREATE POLICY orders_user_policy ON orders
|
||||
**影響:** RLS 查詢快 5-10 倍
|
||||
|
||||
```sql
|
||||
-- ❌ 錯誤:每列呼叫一次函式
|
||||
-- FAIL: 錯誤:每列呼叫一次函式
|
||||
CREATE POLICY orders_policy ON orders
|
||||
USING (auth.uid() = user_id); -- 1M 列呼叫 1M 次!
|
||||
|
||||
-- ✅ 正確:包在 SELECT 中(快取,只呼叫一次)
|
||||
-- PASS: 正確:包在 SELECT 中(快取,只呼叫一次)
|
||||
CREATE POLICY orders_policy ON orders
|
||||
USING ((SELECT auth.uid()) = user_id); -- 快 100 倍
|
||||
|
||||
@@ -235,10 +235,10 @@ CREATE INDEX orders_user_id_idx ON orders (user_id);
|
||||
### 3. 最小權限存取
|
||||
|
||||
```sql
|
||||
-- ❌ 錯誤:過度寬鬆
|
||||
-- FAIL: 錯誤:過度寬鬆
|
||||
GRANT ALL PRIVILEGES ON ALL TABLES TO app_user;
|
||||
|
||||
-- ✅ 正確:最小權限
|
||||
-- PASS: 正確:最小權限
|
||||
CREATE ROLE app_readonly NOLOGIN;
|
||||
GRANT USAGE ON SCHEMA public TO app_readonly;
|
||||
GRANT SELECT ON public.products, public.categories TO app_readonly;
|
||||
@@ -260,36 +260,36 @@ REVOKE ALL ON SCHEMA public FROM public;
|
||||
**影響:** 批量插入快 10-50 倍
|
||||
|
||||
```sql
|
||||
-- ❌ 錯誤:個別插入
|
||||
-- FAIL: 錯誤:個別插入
|
||||
INSERT INTO events (user_id, action) VALUES (1, 'click');
|
||||
INSERT INTO events (user_id, action) VALUES (2, 'view');
|
||||
-- 1000 次往返
|
||||
|
||||
-- ✅ 正確:批次插入
|
||||
-- PASS: 正確:批次插入
|
||||
INSERT INTO events (user_id, action) VALUES
|
||||
(1, 'click'),
|
||||
(2, 'view'),
|
||||
(3, 'click');
|
||||
-- 1 次往返
|
||||
|
||||
-- ✅ 最佳:大資料集用 COPY
|
||||
-- PASS: 最佳:大資料集用 COPY
|
||||
COPY events (user_id, action) FROM '/path/to/data.csv' WITH (FORMAT csv);
|
||||
```
|
||||
|
||||
### 2. 消除 N+1 查詢
|
||||
|
||||
```sql
|
||||
-- ❌ 錯誤:N+1 模式
|
||||
-- FAIL: 錯誤:N+1 模式
|
||||
SELECT id FROM users WHERE active = true; -- 回傳 100 個 IDs
|
||||
-- 然後 100 個查詢:
|
||||
SELECT * FROM orders WHERE user_id = 1;
|
||||
SELECT * FROM orders WHERE user_id = 2;
|
||||
-- ... 還有 98 個
|
||||
|
||||
-- ✅ 正確:用 ANY 的單一查詢
|
||||
-- PASS: 正確:用 ANY 的單一查詢
|
||||
SELECT * FROM orders WHERE user_id = ANY(ARRAY[1, 2, 3, ...]);
|
||||
|
||||
-- ✅ 正確:JOIN
|
||||
-- PASS: 正確:JOIN
|
||||
SELECT u.id, u.name, o.*
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON o.user_id = u.id
|
||||
@@ -301,11 +301,11 @@ WHERE u.active = true;
|
||||
**影響:** 無論頁面深度,一致的 O(1) 效能
|
||||
|
||||
```sql
|
||||
-- ❌ 錯誤:OFFSET 隨深度變慢
|
||||
-- FAIL: 錯誤:OFFSET 隨深度變慢
|
||||
SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 199980;
|
||||
-- 掃描 200,000 列!
|
||||
|
||||
-- ✅ 正確:游標式(總是快)
|
||||
-- PASS: 正確:游標式(總是快)
|
||||
SELECT * FROM products WHERE id > 199980 ORDER BY id LIMIT 20;
|
||||
-- 使用索引,O(1)
|
||||
```
|
||||
@@ -313,11 +313,11 @@ SELECT * FROM products WHERE id > 199980 ORDER BY id LIMIT 20;
|
||||
### 4. UPSERT 用於插入或更新
|
||||
|
||||
```sql
|
||||
-- ❌ 錯誤:競態條件
|
||||
-- FAIL: 錯誤:競態條件
|
||||
SELECT * FROM settings WHERE user_id = 123 AND key = 'theme';
|
||||
-- 兩個執行緒都找不到,都插入,一個失敗
|
||||
|
||||
-- ✅ 正確:原子 UPSERT
|
||||
-- PASS: 正確:原子 UPSERT
|
||||
INSERT INTO settings (user_id, key, value)
|
||||
VALUES (123, 'theme', 'dark')
|
||||
ON CONFLICT (user_id, key)
|
||||
@@ -329,27 +329,27 @@ RETURNING *;
|
||||
|
||||
## 要標記的反模式
|
||||
|
||||
### ❌ 查詢反模式
|
||||
### FAIL: 查詢反模式
|
||||
- 生產程式碼中用 `SELECT *`
|
||||
- WHERE/JOIN 欄位缺少索引
|
||||
- 大表上用 OFFSET 分頁
|
||||
- N+1 查詢模式
|
||||
- 非參數化查詢(SQL 注入風險)
|
||||
|
||||
### ❌ 結構描述反模式
|
||||
### FAIL: 結構描述反模式
|
||||
- IDs 用 `int`(應用 `bigint`)
|
||||
- 無理由用 `varchar(255)`(應用 `text`)
|
||||
- `timestamp` 沒有時區(應用 `timestamptz`)
|
||||
- 隨機 UUIDs 作為主鍵(應用 UUIDv7 或 IDENTITY)
|
||||
- 需要引號的混合大小寫識別符
|
||||
|
||||
### ❌ 安全性反模式
|
||||
### FAIL: 安全性反模式
|
||||
- `GRANT ALL` 給應用程式使用者
|
||||
- 多租戶表缺少 RLS
|
||||
- RLS 政策每列呼叫函式(沒有包在 SELECT 中)
|
||||
- RLS 政策欄位沒有索引
|
||||
|
||||
### ❌ 連線反模式
|
||||
### FAIL: 連線反模式
|
||||
- 沒有連線池
|
||||
- 沒有閒置逾時
|
||||
- Transaction 模式連線池使用 Prepared statements
|
||||
|
||||
@@ -220,28 +220,28 @@ test('market search with complex query', async ({ page }) => {
|
||||
|
||||
**1. 競態條件**
|
||||
```typescript
|
||||
// ❌ 不穩定:不要假設元素已準備好
|
||||
// FAIL: 不穩定:不要假設元素已準備好
|
||||
await page.click('[data-testid="button"]')
|
||||
|
||||
// ✅ 穩定:等待元素準備好
|
||||
// PASS: 穩定:等待元素準備好
|
||||
await page.locator('[data-testid="button"]').click() // 內建自動等待
|
||||
```
|
||||
|
||||
**2. 網路時序**
|
||||
```typescript
|
||||
// ❌ 不穩定:任意逾時
|
||||
// FAIL: 不穩定:任意逾時
|
||||
await page.waitForTimeout(5000)
|
||||
|
||||
// ✅ 穩定:等待特定條件
|
||||
// PASS: 穩定:等待特定條件
|
||||
await page.waitForResponse(resp => resp.url().includes('/api/markets'))
|
||||
```
|
||||
|
||||
**3. 動畫時序**
|
||||
```typescript
|
||||
// ❌ 不穩定:在動畫期間點擊
|
||||
// FAIL: 不穩定:在動畫期間點擊
|
||||
await page.click('[data-testid="menu-item"]')
|
||||
|
||||
// ✅ 穩定:等待動畫完成
|
||||
// PASS: 穩定:等待動畫完成
|
||||
await page.locator('[data-testid="menu-item"]').waitFor({ state: 'visible' })
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.click('[data-testid="menu-item"]')
|
||||
@@ -290,13 +290,13 @@ use: {
|
||||
## 成功指標
|
||||
|
||||
E2E 測試執行後:
|
||||
- ✅ 所有關鍵旅程通過(100%)
|
||||
- ✅ 總體通過率 > 95%
|
||||
- ✅ 不穩定率 < 5%
|
||||
- ✅ 沒有失敗測試阻擋部署
|
||||
- ✅ 產出物已上傳且可存取
|
||||
- ✅ 測試時間 < 10 分鐘
|
||||
- ✅ HTML 報告已產生
|
||||
- PASS: 所有關鍵旅程通過(100%)
|
||||
- PASS: 總體通過率 > 95%
|
||||
- PASS: 不穩定率 < 5%
|
||||
- PASS: 沒有失敗測試阻擋部署
|
||||
- PASS: 產出物已上傳且可存取
|
||||
- PASS: 測試時間 < 10 分鐘
|
||||
- PASS: HTML 報告已產生
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -146,22 +146,22 @@ e) 驗證測試仍通過
|
||||
|
||||
### 1. 未使用的 Imports
|
||||
```typescript
|
||||
// ❌ 移除未使用的 imports
|
||||
// FAIL: 移除未使用的 imports
|
||||
import { useState, useEffect, useMemo } from 'react' // 只有 useState 被使用
|
||||
|
||||
// ✅ 只保留使用的
|
||||
// PASS: 只保留使用的
|
||||
import { useState } from 'react'
|
||||
```
|
||||
|
||||
### 2. 無用程式碼分支
|
||||
```typescript
|
||||
// ❌ 移除不可達的程式碼
|
||||
// FAIL: 移除不可達的程式碼
|
||||
if (false) {
|
||||
// 這永遠不會執行
|
||||
doSomething()
|
||||
}
|
||||
|
||||
// ❌ 移除未使用的函式
|
||||
// FAIL: 移除未使用的函式
|
||||
export function unusedHelper() {
|
||||
// 程式碼庫中沒有參考
|
||||
}
|
||||
@@ -169,18 +169,18 @@ export function unusedHelper() {
|
||||
|
||||
### 3. 重複元件
|
||||
```typescript
|
||||
// ❌ 多個類似元件
|
||||
// FAIL: 多個類似元件
|
||||
components/Button.tsx
|
||||
components/PrimaryButton.tsx
|
||||
components/NewButton.tsx
|
||||
|
||||
// ✅ 整合為一個
|
||||
// PASS: 整合為一個
|
||||
components/Button.tsx(帶 variant prop)
|
||||
```
|
||||
|
||||
### 4. 未使用的相依性
|
||||
```json
|
||||
// ❌ 已安裝但未 import 的套件
|
||||
// FAIL: 已安裝但未 import 的套件
|
||||
{
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21", // 沒有在任何地方使用
|
||||
@@ -261,12 +261,12 @@ components/Button.tsx(帶 variant prop)
|
||||
## 成功指標
|
||||
|
||||
清理工作階段後:
|
||||
- ✅ 所有測試通過
|
||||
- ✅ 建置成功
|
||||
- ✅ 沒有 console 錯誤
|
||||
- ✅ DELETION_LOG.md 已更新
|
||||
- ✅ Bundle 大小減少
|
||||
- ✅ 生產環境沒有回歸
|
||||
- PASS: 所有測試通過
|
||||
- PASS: 建置成功
|
||||
- PASS: 沒有 console 錯誤
|
||||
- PASS: DELETION_LOG.md 已更新
|
||||
- PASS: Bundle 大小減少
|
||||
- PASS: 生產環境沒有回歸
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -128,12 +128,12 @@ b) 審查高風險區域
|
||||
### 1. 寫死密鑰(關鍵)
|
||||
|
||||
```javascript
|
||||
// ❌ 關鍵:寫死的密鑰
|
||||
// FAIL: 關鍵:寫死的密鑰
|
||||
const apiKey = "sk-proj-xxxxx"
|
||||
const password = "admin123"
|
||||
const token = "ghp_xxxxxxxxxxxx"
|
||||
|
||||
// ✅ 正確:環境變數
|
||||
// PASS: 正確:環境變數
|
||||
const apiKey = process.env.OPENAI_API_KEY
|
||||
if (!apiKey) {
|
||||
throw new Error('OPENAI_API_KEY not configured')
|
||||
@@ -143,11 +143,11 @@ if (!apiKey) {
|
||||
### 2. SQL 注入(關鍵)
|
||||
|
||||
```javascript
|
||||
// ❌ 關鍵:SQL 注入弱點
|
||||
// FAIL: 關鍵:SQL 注入弱點
|
||||
const query = `SELECT * FROM users WHERE id = ${userId}`
|
||||
await db.query(query)
|
||||
|
||||
// ✅ 正確:參數化查詢
|
||||
// PASS: 正確:參數化查詢
|
||||
const { data } = await supabase
|
||||
.from('users')
|
||||
.select('*')
|
||||
@@ -157,11 +157,11 @@ const { data } = await supabase
|
||||
### 3. 命令注入(關鍵)
|
||||
|
||||
```javascript
|
||||
// ❌ 關鍵:命令注入
|
||||
// FAIL: 關鍵:命令注入
|
||||
const { exec } = require('child_process')
|
||||
exec(`ping ${userInput}`, callback)
|
||||
|
||||
// ✅ 正確:使用函式庫,而非 shell 命令
|
||||
// PASS: 正確:使用函式庫,而非 shell 命令
|
||||
const dns = require('dns')
|
||||
dns.lookup(userInput, callback)
|
||||
```
|
||||
@@ -169,10 +169,10 @@ dns.lookup(userInput, callback)
|
||||
### 4. 跨站腳本 XSS(高)
|
||||
|
||||
```javascript
|
||||
// ❌ 高:XSS 弱點
|
||||
// FAIL: 高:XSS 弱點
|
||||
element.innerHTML = userInput
|
||||
|
||||
// ✅ 正確:使用 textContent 或清理
|
||||
// PASS: 正確:使用 textContent 或清理
|
||||
element.textContent = userInput
|
||||
// 或
|
||||
import DOMPurify from 'dompurify'
|
||||
@@ -182,10 +182,10 @@ element.innerHTML = DOMPurify.sanitize(userInput)
|
||||
### 5. 伺服器端請求偽造 SSRF(高)
|
||||
|
||||
```javascript
|
||||
// ❌ 高:SSRF 弱點
|
||||
// FAIL: 高:SSRF 弱點
|
||||
const response = await fetch(userProvidedUrl)
|
||||
|
||||
// ✅ 正確:驗證和白名單 URL
|
||||
// PASS: 正確:驗證和白名單 URL
|
||||
const allowedDomains = ['api.example.com', 'cdn.example.com']
|
||||
const url = new URL(userProvidedUrl)
|
||||
if (!allowedDomains.includes(url.hostname)) {
|
||||
@@ -197,10 +197,10 @@ const response = await fetch(url.toString())
|
||||
### 6. 不安全的驗證(關鍵)
|
||||
|
||||
```javascript
|
||||
// ❌ 關鍵:明文密碼比對
|
||||
// FAIL: 關鍵:明文密碼比對
|
||||
if (password === storedPassword) { /* login */ }
|
||||
|
||||
// ✅ 正確:雜湊密碼比對
|
||||
// PASS: 正確:雜湊密碼比對
|
||||
import bcrypt from 'bcrypt'
|
||||
const isValid = await bcrypt.compare(password, hashedPassword)
|
||||
```
|
||||
@@ -208,13 +208,13 @@ const isValid = await bcrypt.compare(password, hashedPassword)
|
||||
### 7. 授權不足(關鍵)
|
||||
|
||||
```javascript
|
||||
// ❌ 關鍵:沒有授權檢查
|
||||
// FAIL: 關鍵:沒有授權檢查
|
||||
app.get('/api/user/:id', async (req, res) => {
|
||||
const user = await getUser(req.params.id)
|
||||
res.json(user)
|
||||
})
|
||||
|
||||
// ✅ 正確:驗證使用者可以存取資源
|
||||
// PASS: 正確:驗證使用者可以存取資源
|
||||
app.get('/api/user/:id', authenticateUser, async (req, res) => {
|
||||
if (req.user.id !== req.params.id && !req.user.isAdmin) {
|
||||
return res.status(403).json({ error: 'Forbidden' })
|
||||
@@ -227,13 +227,13 @@ app.get('/api/user/:id', authenticateUser, async (req, res) => {
|
||||
### 8. 財務操作中的競態條件(關鍵)
|
||||
|
||||
```javascript
|
||||
// ❌ 關鍵:餘額檢查中的競態條件
|
||||
// FAIL: 關鍵:餘額檢查中的競態條件
|
||||
const balance = await getBalance(userId)
|
||||
if (balance >= amount) {
|
||||
await withdraw(userId, amount) // 另一個請求可能同時提款!
|
||||
}
|
||||
|
||||
// ✅ 正確:帶鎖定的原子交易
|
||||
// PASS: 正確:帶鎖定的原子交易
|
||||
await db.transaction(async (trx) => {
|
||||
const balance = await trx('balances')
|
||||
.where({ user_id: userId })
|
||||
@@ -253,13 +253,13 @@ await db.transaction(async (trx) => {
|
||||
### 9. 速率限制不足(高)
|
||||
|
||||
```javascript
|
||||
// ❌ 高:沒有速率限制
|
||||
// FAIL: 高:沒有速率限制
|
||||
app.post('/api/trade', async (req, res) => {
|
||||
await executeTrade(req.body)
|
||||
res.json({ success: true })
|
||||
})
|
||||
|
||||
// ✅ 正確:速率限制
|
||||
// PASS: 正確:速率限制
|
||||
import rateLimit from 'express-rate-limit'
|
||||
|
||||
const tradeLimiter = rateLimit({
|
||||
@@ -277,10 +277,10 @@ app.post('/api/trade', tradeLimiter, async (req, res) => {
|
||||
### 10. 記錄敏感資料(中)
|
||||
|
||||
```javascript
|
||||
// ❌ 中:記錄敏感資料
|
||||
// FAIL: 中:記錄敏感資料
|
||||
console.log('User login:', { email, password, apiKey })
|
||||
|
||||
// ✅ 正確:清理日誌
|
||||
// PASS: 正確:清理日誌
|
||||
console.log('User login:', {
|
||||
email: email.replace(/(?<=.).(?=.*@)/g, '*'),
|
||||
passwordProvided: !!password
|
||||
@@ -302,7 +302,7 @@ console.log('User login:', {
|
||||
- **高優先問題:** Y
|
||||
- **中優先問題:** Z
|
||||
- **低優先問題:** W
|
||||
- **風險等級:** 🔴 高 / 🟡 中 / 🟢 低
|
||||
- **風險等級:** 高 / 中 / 低
|
||||
|
||||
## 關鍵問題(立即修復)
|
||||
|
||||
@@ -324,7 +324,7 @@ console.log('User login:', {
|
||||
|
||||
**修復:**
|
||||
```javascript
|
||||
// ✅ 安全的實作
|
||||
// PASS: 安全的實作
|
||||
```
|
||||
|
||||
**參考:**
|
||||
@@ -365,13 +365,13 @@ console.log('User login:', {
|
||||
## 成功指標
|
||||
|
||||
安全性審查後:
|
||||
- ✅ 未發現關鍵問題
|
||||
- ✅ 所有高優先問題已處理
|
||||
- ✅ 安全性檢查清單完成
|
||||
- ✅ 程式碼中無密鑰
|
||||
- ✅ 相依性已更新
|
||||
- ✅ 測試包含安全性情境
|
||||
- ✅ 文件已更新
|
||||
- PASS: 未發現關鍵問題
|
||||
- PASS: 所有高優先問題已處理
|
||||
- PASS: 安全性檢查清單完成
|
||||
- PASS: 程式碼中無密鑰
|
||||
- PASS: 相依性已更新
|
||||
- PASS: 測試包含安全性情境
|
||||
- PASS: 文件已更新
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -220,26 +220,26 @@ jest.mock('@/lib/openai', () => ({
|
||||
|
||||
## 測試異味(反模式)
|
||||
|
||||
### ❌ 測試實作細節
|
||||
### FAIL: 測試實作細節
|
||||
```typescript
|
||||
// 不要測試內部狀態
|
||||
expect(component.state.count).toBe(5)
|
||||
```
|
||||
|
||||
### ✅ 測試使用者可見的行為
|
||||
### PASS: 測試使用者可見的行為
|
||||
```typescript
|
||||
// 測試使用者看到的
|
||||
expect(screen.getByText('Count: 5')).toBeInTheDocument()
|
||||
```
|
||||
|
||||
### ❌ 測試相互依賴
|
||||
### FAIL: 測試相互依賴
|
||||
```typescript
|
||||
// 不要依賴前一個測試
|
||||
test('creates user', () => { /* ... */ })
|
||||
test('updates same user', () => { /* 需要前一個測試 */ })
|
||||
```
|
||||
|
||||
### ✅ 獨立測試
|
||||
### PASS: 獨立測試
|
||||
```typescript
|
||||
// 在每個測試中設定資料
|
||||
test('updates user', () => {
|
||||
|
||||
@@ -65,20 +65,20 @@ open artifacts/search-results.png
|
||||
## 最佳實務
|
||||
|
||||
**應該做:**
|
||||
- ✅ 使用 Page Object Model 以利維護
|
||||
- ✅ 使用 data-testid 屬性作為選擇器
|
||||
- ✅ 等待 API 回應,不要用任意逾時
|
||||
- ✅ 測試關鍵使用者旅程端對端
|
||||
- ✅ 合併到主分支前執行測試
|
||||
- ✅ 測試失敗時審查產出物
|
||||
- PASS: 使用 Page Object Model 以利維護
|
||||
- PASS: 使用 data-testid 屬性作為選擇器
|
||||
- PASS: 等待 API 回應,不要用任意逾時
|
||||
- PASS: 測試關鍵使用者旅程端對端
|
||||
- PASS: 合併到主分支前執行測試
|
||||
- PASS: 測試失敗時審查產出物
|
||||
|
||||
**不應該做:**
|
||||
- ❌ 使用脆弱的選擇器(CSS class 可能改變)
|
||||
- ❌ 測試實作細節
|
||||
- ❌ 對生產環境執行測試
|
||||
- ❌ 忽略不穩定的測試
|
||||
- ❌ 失敗時跳過產出物審查
|
||||
- ❌ 用 E2E 測試每個邊界情況(使用單元測試)
|
||||
- FAIL: 使用脆弱的選擇器(CSS class 可能改變)
|
||||
- FAIL: 測試實作細節
|
||||
- FAIL: 對生產環境執行測試
|
||||
- FAIL: 忽略不穩定的測試
|
||||
- FAIL: 失敗時跳過產出物審查
|
||||
- FAIL: 用 E2E 測試每個邊界情況(使用單元測試)
|
||||
|
||||
## 快速指令
|
||||
|
||||
|
||||
@@ -70,9 +70,9 @@ govulncheck ./...
|
||||
|
||||
| 狀態 | 條件 |
|
||||
|------|------|
|
||||
| ✅ 批准 | 沒有關鍵或高優先問題 |
|
||||
| ⚠️ 警告 | 只有中優先問題(謹慎合併)|
|
||||
| ❌ 阻擋 | 發現關鍵或高優先問題 |
|
||||
| PASS: 批准 | 沒有關鍵或高優先問題 |
|
||||
| WARNING: 警告 | 只有中優先問題(謹慎合併)|
|
||||
| FAIL: 阻擋 | 發現關鍵或高優先問題 |
|
||||
|
||||
## 與其他指令的整合
|
||||
|
||||
|
||||
@@ -49,20 +49,20 @@ REPEAT: 下一個功能/情境
|
||||
## TDD 最佳實務
|
||||
|
||||
**應該做:**
|
||||
- ✅ 在任何實作前先撰寫測試
|
||||
- ✅ 在實作前執行測試並驗證它們失敗
|
||||
- ✅ 撰寫最小程式碼使測試通過
|
||||
- ✅ 只在測試通過後才重構
|
||||
- ✅ 新增邊界情況和錯誤情境
|
||||
- ✅ 目標 80% 以上覆蓋率(關鍵程式碼 100%)
|
||||
- PASS: 在任何實作前先撰寫測試
|
||||
- PASS: 在實作前執行測試並驗證它們失敗
|
||||
- PASS: 撰寫最小程式碼使測試通過
|
||||
- PASS: 只在測試通過後才重構
|
||||
- PASS: 新增邊界情況和錯誤情境
|
||||
- PASS: 目標 80% 以上覆蓋率(關鍵程式碼 100%)
|
||||
|
||||
**不應該做:**
|
||||
- ❌ 在測試之前撰寫實作
|
||||
- ❌ 跳過每次變更後執行測試
|
||||
- ❌ 一次撰寫太多程式碼
|
||||
- ❌ 忽略失敗的測試
|
||||
- ❌ 測試實作細節(測試行為)
|
||||
- ❌ Mock 所有東西(優先使用整合測試)
|
||||
- FAIL: 在測試之前撰寫實作
|
||||
- FAIL: 跳過每次變更後執行測試
|
||||
- FAIL: 一次撰寫太多程式碼
|
||||
- FAIL: 忽略失敗的測試
|
||||
- FAIL: 測試實作細節(測試行為)
|
||||
- FAIL: Mock 所有東西(優先使用整合測試)
|
||||
|
||||
## 覆蓋率要求
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ description: Backend architecture patterns, API design, database optimization, a
|
||||
### RESTful API 結構
|
||||
|
||||
```typescript
|
||||
// ✅ 基於資源的 URL
|
||||
// PASS: 基於資源的 URL
|
||||
GET /api/markets # 列出資源
|
||||
GET /api/markets/:id # 取得單一資源
|
||||
POST /api/markets # 建立資源
|
||||
@@ -20,7 +20,7 @@ PUT /api/markets/:id # 替換資源
|
||||
PATCH /api/markets/:id # 更新資源
|
||||
DELETE /api/markets/:id # 刪除資源
|
||||
|
||||
// ✅ 用於過濾、排序、分頁的查詢參數
|
||||
// PASS: 用於過濾、排序、分頁的查詢參數
|
||||
GET /api/markets?status=active&sort=volume&limit=20&offset=0
|
||||
```
|
||||
|
||||
@@ -120,7 +120,7 @@ export default withAuth(async (req, res) => {
|
||||
### 查詢優化
|
||||
|
||||
```typescript
|
||||
// ✅ 良好:只選擇需要的欄位
|
||||
// PASS: 良好:只選擇需要的欄位
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('id, name, status, volume')
|
||||
@@ -128,7 +128,7 @@ const { data } = await supabase
|
||||
.order('volume', { ascending: false })
|
||||
.limit(10)
|
||||
|
||||
// ❌ 不良:選擇所有欄位
|
||||
// FAIL: 不良:選擇所有欄位
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('*')
|
||||
@@ -137,13 +137,13 @@ const { data } = await supabase
|
||||
### N+1 查詢問題預防
|
||||
|
||||
```typescript
|
||||
// ❌ 不良:N+1 查詢問題
|
||||
// FAIL: 不良:N+1 查詢問題
|
||||
const markets = await getMarkets()
|
||||
for (const market of markets) {
|
||||
market.creator = await getUser(market.creator_id) // N 次查詢
|
||||
}
|
||||
|
||||
// ✅ 良好:批次取得
|
||||
// PASS: 良好:批次取得
|
||||
const markets = await getMarkets()
|
||||
const creatorIds = markets.map(m => m.creator_id)
|
||||
const creators = await getUsers(creatorIds) // 1 次查詢
|
||||
|
||||
@@ -86,7 +86,7 @@ ORDER BY hour DESC;
|
||||
### 高效過濾
|
||||
|
||||
```sql
|
||||
-- ✅ 良好:先使用索引欄位
|
||||
-- PASS: 良好:先使用索引欄位
|
||||
SELECT *
|
||||
FROM markets_analytics
|
||||
WHERE date >= '2025-01-01'
|
||||
@@ -95,7 +95,7 @@ WHERE date >= '2025-01-01'
|
||||
ORDER BY date DESC
|
||||
LIMIT 100;
|
||||
|
||||
-- ❌ 不良:先過濾非索引欄位
|
||||
-- FAIL: 不良:先過濾非索引欄位
|
||||
SELECT *
|
||||
FROM markets_analytics
|
||||
WHERE volume > 1000
|
||||
@@ -106,7 +106,7 @@ WHERE volume > 1000
|
||||
### 聚合
|
||||
|
||||
```sql
|
||||
-- ✅ 良好:使用 ClickHouse 特定聚合函式
|
||||
-- PASS: 良好:使用 ClickHouse 特定聚合函式
|
||||
SELECT
|
||||
toStartOfDay(created_at) AS day,
|
||||
market_id,
|
||||
@@ -119,7 +119,7 @@ WHERE created_at >= today() - INTERVAL 7 DAY
|
||||
GROUP BY day, market_id
|
||||
ORDER BY day DESC, total_volume DESC;
|
||||
|
||||
-- ✅ 使用 quantile 計算百分位數(比 percentile 更高效)
|
||||
-- PASS: 使用 quantile 計算百分位數(比 percentile 更高效)
|
||||
SELECT
|
||||
quantile(0.50)(trade_size) AS median,
|
||||
quantile(0.95)(trade_size) AS p95,
|
||||
@@ -162,7 +162,7 @@ const clickhouse = new ClickHouse({
|
||||
}
|
||||
})
|
||||
|
||||
// ✅ 批量插入(高效)
|
||||
// PASS: 批量插入(高效)
|
||||
async function bulkInsertTrades(trades: Trade[]) {
|
||||
const values = trades.map(trade => `(
|
||||
'${trade.id}',
|
||||
@@ -178,7 +178,7 @@ async function bulkInsertTrades(trades: Trade[]) {
|
||||
`).toPromise()
|
||||
}
|
||||
|
||||
// ❌ 個別插入(慢)
|
||||
// FAIL: 個別插入(慢)
|
||||
async function insertTrade(trade: Trade) {
|
||||
// 不要在迴圈中這樣做!
|
||||
await clickhouse.query(`
|
||||
|
||||
@@ -38,12 +38,12 @@ description: Universal coding standards, best practices, and patterns for TypeSc
|
||||
### 變數命名
|
||||
|
||||
```typescript
|
||||
// ✅ 良好:描述性名稱
|
||||
// PASS: 良好:描述性名稱
|
||||
const marketSearchQuery = 'election'
|
||||
const isUserAuthenticated = true
|
||||
const totalRevenue = 1000
|
||||
|
||||
// ❌ 不良:不清楚的名稱
|
||||
// FAIL: 不良:不清楚的名稱
|
||||
const q = 'election'
|
||||
const flag = true
|
||||
const x = 1000
|
||||
@@ -52,12 +52,12 @@ const x = 1000
|
||||
### 函式命名
|
||||
|
||||
```typescript
|
||||
// ✅ 良好:動詞-名詞模式
|
||||
// PASS: 良好:動詞-名詞模式
|
||||
async function fetchMarketData(marketId: string) { }
|
||||
function calculateSimilarity(a: number[], b: number[]) { }
|
||||
function isValidEmail(email: string): boolean { }
|
||||
|
||||
// ❌ 不良:不清楚或只有名詞
|
||||
// FAIL: 不良:不清楚或只有名詞
|
||||
async function market(id: string) { }
|
||||
function similarity(a, b) { }
|
||||
function email(e) { }
|
||||
@@ -66,7 +66,7 @@ function email(e) { }
|
||||
### 不可變性模式(關鍵)
|
||||
|
||||
```typescript
|
||||
// ✅ 總是使用展開運算符
|
||||
// PASS: 總是使用展開運算符
|
||||
const updatedUser = {
|
||||
...user,
|
||||
name: 'New Name'
|
||||
@@ -74,7 +74,7 @@ const updatedUser = {
|
||||
|
||||
const updatedArray = [...items, newItem]
|
||||
|
||||
// ❌ 永遠不要直接修改
|
||||
// FAIL: 永遠不要直接修改
|
||||
user.name = 'New Name' // 不良
|
||||
items.push(newItem) // 不良
|
||||
```
|
||||
@@ -82,7 +82,7 @@ items.push(newItem) // 不良
|
||||
### 錯誤處理
|
||||
|
||||
```typescript
|
||||
// ✅ 良好:完整的錯誤處理
|
||||
// PASS: 良好:完整的錯誤處理
|
||||
async function fetchData(url: string) {
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
@@ -98,7 +98,7 @@ async function fetchData(url: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 不良:無錯誤處理
|
||||
// FAIL: 不良:無錯誤處理
|
||||
async function fetchData(url) {
|
||||
const response = await fetch(url)
|
||||
return response.json()
|
||||
@@ -108,14 +108,14 @@ async function fetchData(url) {
|
||||
### Async/Await 最佳實務
|
||||
|
||||
```typescript
|
||||
// ✅ 良好:可能時並行執行
|
||||
// PASS: 良好:可能時並行執行
|
||||
const [users, markets, stats] = await Promise.all([
|
||||
fetchUsers(),
|
||||
fetchMarkets(),
|
||||
fetchStats()
|
||||
])
|
||||
|
||||
// ❌ 不良:不必要的順序執行
|
||||
// FAIL: 不良:不必要的順序執行
|
||||
const users = await fetchUsers()
|
||||
const markets = await fetchMarkets()
|
||||
const stats = await fetchStats()
|
||||
@@ -124,7 +124,7 @@ const stats = await fetchStats()
|
||||
### 型別安全
|
||||
|
||||
```typescript
|
||||
// ✅ 良好:正確的型別
|
||||
// PASS: 良好:正確的型別
|
||||
interface Market {
|
||||
id: string
|
||||
name: string
|
||||
@@ -136,7 +136,7 @@ function getMarket(id: string): Promise<Market> {
|
||||
// 實作
|
||||
}
|
||||
|
||||
// ❌ 不良:使用 'any'
|
||||
// FAIL: 不良:使用 'any'
|
||||
function getMarket(id: any): Promise<any> {
|
||||
// 實作
|
||||
}
|
||||
@@ -147,7 +147,7 @@ function getMarket(id: any): Promise<any> {
|
||||
### 元件結構
|
||||
|
||||
```typescript
|
||||
// ✅ 良好:具有型別的函式元件
|
||||
// PASS: 良好:具有型別的函式元件
|
||||
interface ButtonProps {
|
||||
children: React.ReactNode
|
||||
onClick: () => void
|
||||
@@ -172,7 +172,7 @@ export function Button({
|
||||
)
|
||||
}
|
||||
|
||||
// ❌ 不良:無型別、結構不清楚
|
||||
// FAIL: 不良:無型別、結構不清楚
|
||||
export function Button(props) {
|
||||
return <button onClick={props.onClick}>{props.children}</button>
|
||||
}
|
||||
@@ -181,7 +181,7 @@ export function Button(props) {
|
||||
### 自訂 Hooks
|
||||
|
||||
```typescript
|
||||
// ✅ 良好:可重用的自訂 hook
|
||||
// PASS: 良好:可重用的自訂 hook
|
||||
export function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
||||
|
||||
@@ -203,25 +203,25 @@ const debouncedQuery = useDebounce(searchQuery, 500)
|
||||
### 狀態管理
|
||||
|
||||
```typescript
|
||||
// ✅ 良好:正確的狀態更新
|
||||
// PASS: 良好:正確的狀態更新
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
// 基於先前狀態的函式更新
|
||||
setCount(prev => prev + 1)
|
||||
|
||||
// ❌ 不良:直接引用狀態
|
||||
// FAIL: 不良:直接引用狀態
|
||||
setCount(count + 1) // 在非同步情境中可能過時
|
||||
```
|
||||
|
||||
### 條件渲染
|
||||
|
||||
```typescript
|
||||
// ✅ 良好:清晰的條件渲染
|
||||
// PASS: 良好:清晰的條件渲染
|
||||
{isLoading && <Spinner />}
|
||||
{error && <ErrorMessage error={error} />}
|
||||
{data && <DataDisplay data={data} />}
|
||||
|
||||
// ❌ 不良:三元地獄
|
||||
// FAIL: 不良:三元地獄
|
||||
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
|
||||
```
|
||||
|
||||
@@ -244,7 +244,7 @@ GET /api/markets?status=active&limit=10&offset=0
|
||||
### 回應格式
|
||||
|
||||
```typescript
|
||||
// ✅ 良好:一致的回應結構
|
||||
// PASS: 良好:一致的回應結構
|
||||
interface ApiResponse<T> {
|
||||
success: boolean
|
||||
data?: T
|
||||
@@ -275,7 +275,7 @@ return NextResponse.json({
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
|
||||
// ✅ 良好:Schema 驗證
|
||||
// PASS: 良好:Schema 驗證
|
||||
const CreateMarketSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
description: z.string().min(1).max(2000),
|
||||
@@ -338,14 +338,14 @@ types/market.types.ts # 型別用 camelCase 加 .types 後綴
|
||||
### 何時註解
|
||||
|
||||
```typescript
|
||||
// ✅ 良好:解釋「為什麼」而非「什麼」
|
||||
// PASS: 良好:解釋「為什麼」而非「什麼」
|
||||
// 使用指數退避以避免在服務中斷時壓垮 API
|
||||
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
|
||||
|
||||
// 為了處理大陣列的效能,此處刻意使用突變
|
||||
items.push(newItem)
|
||||
|
||||
// ❌ 不良:陳述顯而易見的事實
|
||||
// FAIL: 不良:陳述顯而易見的事實
|
||||
// 將計數器加 1
|
||||
count++
|
||||
|
||||
@@ -385,12 +385,12 @@ export async function searchMarkets(
|
||||
```typescript
|
||||
import { useMemo, useCallback } from 'react'
|
||||
|
||||
// ✅ 良好:記憶化昂貴的計算
|
||||
// PASS: 良好:記憶化昂貴的計算
|
||||
const sortedMarkets = useMemo(() => {
|
||||
return markets.sort((a, b) => b.volume - a.volume)
|
||||
}, [markets])
|
||||
|
||||
// ✅ 良好:記憶化回呼函式
|
||||
// PASS: 良好:記憶化回呼函式
|
||||
const handleSearch = useCallback((query: string) => {
|
||||
setSearchQuery(query)
|
||||
}, [])
|
||||
@@ -401,7 +401,7 @@ const handleSearch = useCallback((query: string) => {
|
||||
```typescript
|
||||
import { lazy, Suspense } from 'react'
|
||||
|
||||
// ✅ 良好:延遲載入重型元件
|
||||
// PASS: 良好:延遲載入重型元件
|
||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||
|
||||
export function Dashboard() {
|
||||
@@ -416,13 +416,13 @@ export function Dashboard() {
|
||||
### 資料庫查詢
|
||||
|
||||
```typescript
|
||||
// ✅ 良好:只選擇需要的欄位
|
||||
// PASS: 良好:只選擇需要的欄位
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('id, name, status')
|
||||
.limit(10)
|
||||
|
||||
// ❌ 不良:選擇所有欄位
|
||||
// FAIL: 不良:選擇所有欄位
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('*')
|
||||
@@ -449,12 +449,12 @@ test('calculates similarity correctly', () => {
|
||||
### 測試命名
|
||||
|
||||
```typescript
|
||||
// ✅ 良好:描述性測試名稱
|
||||
// PASS: 良好:描述性測試名稱
|
||||
test('returns empty array when no markets match query', () => { })
|
||||
test('throws error when OpenAI API key is missing', () => { })
|
||||
test('falls back to substring search when Redis unavailable', () => { })
|
||||
|
||||
// ❌ 不良:模糊的測試名稱
|
||||
// FAIL: 不良:模糊的測試名稱
|
||||
test('works', () => { })
|
||||
test('test search', () => { })
|
||||
```
|
||||
@@ -465,12 +465,12 @@ test('test search', () => { })
|
||||
|
||||
### 1. 過長函式
|
||||
```typescript
|
||||
// ❌ 不良:函式超過 50 行
|
||||
// FAIL: 不良:函式超過 50 行
|
||||
function processMarketData() {
|
||||
// 100 行程式碼
|
||||
}
|
||||
|
||||
// ✅ 良好:拆分為較小的函式
|
||||
// PASS: 良好:拆分為較小的函式
|
||||
function processMarketData() {
|
||||
const validated = validateData()
|
||||
const transformed = transformData(validated)
|
||||
@@ -480,7 +480,7 @@ function processMarketData() {
|
||||
|
||||
### 2. 過深巢狀
|
||||
```typescript
|
||||
// ❌ 不良:5 層以上巢狀
|
||||
// FAIL: 不良:5 層以上巢狀
|
||||
if (user) {
|
||||
if (user.isAdmin) {
|
||||
if (market) {
|
||||
@@ -493,7 +493,7 @@ if (user) {
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 良好:提前返回
|
||||
// PASS: 良好:提前返回
|
||||
if (!user) return
|
||||
if (!user.isAdmin) return
|
||||
if (!market) return
|
||||
@@ -505,11 +505,11 @@ if (!hasPermission) return
|
||||
|
||||
### 3. 魔術數字
|
||||
```typescript
|
||||
// ❌ 不良:無解釋的數字
|
||||
// FAIL: 不良:無解釋的數字
|
||||
if (retryCount > 3) { }
|
||||
setTimeout(callback, 500)
|
||||
|
||||
// ✅ 良好:命名常數
|
||||
// PASS: 良好:命名常數
|
||||
const MAX_RETRIES = 3
|
||||
const DEBOUNCE_DELAY_MS = 500
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ description: Frontend development patterns for React, Next.js, state management,
|
||||
### 組合優於繼承
|
||||
|
||||
```typescript
|
||||
// ✅ 良好:元件組合
|
||||
// PASS: 良好:元件組合
|
||||
interface CardProps {
|
||||
children: React.ReactNode
|
||||
variant?: 'default' | 'outlined'
|
||||
@@ -283,17 +283,17 @@ export function useMarkets() {
|
||||
### 記憶化
|
||||
|
||||
```typescript
|
||||
// ✅ useMemo 用於昂貴計算
|
||||
// PASS: useMemo 用於昂貴計算
|
||||
const sortedMarkets = useMemo(() => {
|
||||
return markets.sort((a, b) => b.volume - a.volume)
|
||||
}, [markets])
|
||||
|
||||
// ✅ useCallback 用於傳遞給子元件的函式
|
||||
// PASS: useCallback 用於傳遞給子元件的函式
|
||||
const handleSearch = useCallback((query: string) => {
|
||||
setSearchQuery(query)
|
||||
}, [])
|
||||
|
||||
// ✅ React.memo 用於純元件
|
||||
// PASS: React.memo 用於純元件
|
||||
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
||||
return (
|
||||
<div className="market-card">
|
||||
@@ -309,7 +309,7 @@ export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
||||
```typescript
|
||||
import { lazy, Suspense } from 'react'
|
||||
|
||||
// ✅ 延遲載入重型元件
|
||||
// PASS: 延遲載入重型元件
|
||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
|
||||
|
||||
@@ -504,7 +504,7 @@ export class ErrorBoundary extends React.Component<
|
||||
```typescript
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
|
||||
// ✅ 列表動畫
|
||||
// PASS: 列表動畫
|
||||
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
@@ -523,7 +523,7 @@ export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
||||
)
|
||||
}
|
||||
|
||||
// ✅ Modal 動畫
|
||||
// PASS: Modal 動畫
|
||||
export function Modal({ isOpen, onClose, children }: ModalProps) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
|
||||
@@ -27,12 +27,12 @@ description: Pattern for progressively refining context retrieval to solve the s
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ DISPATCH │─────▶│ EVALUATE │ │
|
||||
│ │ DISPATCH │─────│ EVALUATE │ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ ▲ │ │
|
||||
│ │ ▼ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ LOOP │◀─────│ REFINE │ │
|
||||
│ │ LOOP │─────│ REFINE │ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ │
|
||||
│ 最多 3 個循環,然後繼續 │
|
||||
|
||||
@@ -21,13 +21,13 @@ description: Use this skill when adding authentication, handling user input, wor
|
||||
|
||||
### 1. 密鑰管理
|
||||
|
||||
#### ❌ 絕不這樣做
|
||||
#### FAIL: 絕不這樣做
|
||||
```typescript
|
||||
const apiKey = "sk-proj-xxxxx" // 寫死的密鑰
|
||||
const dbPassword = "password123" // 在原始碼中
|
||||
```
|
||||
|
||||
#### ✅ 總是這樣做
|
||||
#### PASS: 總是這樣做
|
||||
```typescript
|
||||
const apiKey = process.env.OPENAI_API_KEY
|
||||
const dbUrl = process.env.DATABASE_URL
|
||||
@@ -107,14 +107,14 @@ function validateFileUpload(file: File) {
|
||||
|
||||
### 3. SQL 注入預防
|
||||
|
||||
#### ❌ 絕不串接 SQL
|
||||
#### FAIL: 絕不串接 SQL
|
||||
```typescript
|
||||
// 危險 - SQL 注入漏洞
|
||||
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
|
||||
await db.query(query)
|
||||
```
|
||||
|
||||
#### ✅ 總是使用參數化查詢
|
||||
#### PASS: 總是使用參數化查詢
|
||||
```typescript
|
||||
// 安全 - 參數化查詢
|
||||
const { data } = await supabase
|
||||
@@ -139,10 +139,10 @@ await db.query(
|
||||
|
||||
#### JWT Token 處理
|
||||
```typescript
|
||||
// ❌ 錯誤:localStorage(易受 XSS 攻擊)
|
||||
// FAIL: 錯誤:localStorage(易受 XSS 攻擊)
|
||||
localStorage.setItem('token', token)
|
||||
|
||||
// ✅ 正確:httpOnly cookies
|
||||
// PASS: 正確:httpOnly cookies
|
||||
res.setHeader('Set-Cookie',
|
||||
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
|
||||
```
|
||||
@@ -299,18 +299,18 @@ app.use('/api/search', searchLimiter)
|
||||
|
||||
#### 日誌記錄
|
||||
```typescript
|
||||
// ❌ 錯誤:記錄敏感資料
|
||||
// FAIL: 錯誤:記錄敏感資料
|
||||
console.log('User login:', { email, password })
|
||||
console.log('Payment:', { cardNumber, cvv })
|
||||
|
||||
// ✅ 正確:遮蔽敏感資料
|
||||
// PASS: 正確:遮蔽敏感資料
|
||||
console.log('User login:', { email, userId })
|
||||
console.log('Payment:', { last4: card.last4, userId })
|
||||
```
|
||||
|
||||
#### 錯誤訊息
|
||||
```typescript
|
||||
// ❌ 錯誤:暴露內部細節
|
||||
// FAIL: 錯誤:暴露內部細節
|
||||
catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: error.message, stack: error.stack },
|
||||
@@ -318,7 +318,7 @@ catch (error) {
|
||||
)
|
||||
}
|
||||
|
||||
// ✅ 正確:通用錯誤訊息
|
||||
// PASS: 正確:通用錯誤訊息
|
||||
catch (error) {
|
||||
console.error('Internal error:', error)
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#### 最小權限原則
|
||||
|
||||
```yaml
|
||||
# ✅ 正確:最小權限
|
||||
# PASS: 正確:最小權限
|
||||
iam_role:
|
||||
permissions:
|
||||
- s3:GetObject # 只有讀取存取
|
||||
@@ -32,7 +32,7 @@ iam_role:
|
||||
resources:
|
||||
- arn:aws:s3:::my-bucket/* # 只有特定 bucket
|
||||
|
||||
# ❌ 錯誤:過於廣泛的權限
|
||||
# FAIL: 錯誤:過於廣泛的權限
|
||||
iam_role:
|
||||
permissions:
|
||||
- s3:* # 所有 S3 動作
|
||||
@@ -65,14 +65,14 @@ aws iam enable-mfa-device \
|
||||
#### 雲端密鑰管理器
|
||||
|
||||
```typescript
|
||||
// ✅ 正確:使用雲端密鑰管理器
|
||||
// PASS: 正確:使用雲端密鑰管理器
|
||||
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
|
||||
|
||||
const client = new SecretsManager({ region: 'us-east-1' });
|
||||
const secret = await client.getSecretValue({ SecretId: 'prod/api-key' });
|
||||
const apiKey = JSON.parse(secret.SecretString).key;
|
||||
|
||||
// ❌ 錯誤:寫死或只在環境變數
|
||||
// FAIL: 錯誤:寫死或只在環境變數
|
||||
const apiKey = process.env.API_KEY; // 未輪換、未稽核
|
||||
```
|
||||
|
||||
@@ -99,7 +99,7 @@ aws secretsmanager rotate-secret \
|
||||
#### VPC 和防火牆設定
|
||||
|
||||
```terraform
|
||||
# ✅ 正確:限制的安全群組
|
||||
# PASS: 正確:限制的安全群組
|
||||
resource "aws_security_group" "app" {
|
||||
name = "app-sg"
|
||||
|
||||
@@ -118,7 +118,7 @@ resource "aws_security_group" "app" {
|
||||
}
|
||||
}
|
||||
|
||||
# ❌ 錯誤:對網際網路開放
|
||||
# FAIL: 錯誤:對網際網路開放
|
||||
resource "aws_security_group" "bad" {
|
||||
ingress {
|
||||
from_port = 0
|
||||
@@ -142,7 +142,7 @@ resource "aws_security_group" "bad" {
|
||||
#### CloudWatch/日誌設定
|
||||
|
||||
```typescript
|
||||
// ✅ 正確:全面日誌記錄
|
||||
// PASS: 正確:全面日誌記錄
|
||||
import { CloudWatchLogsClient, CreateLogStreamCommand } from '@aws-sdk/client-cloudwatch-logs';
|
||||
|
||||
const logSecurityEvent = async (event: SecurityEvent) => {
|
||||
@@ -177,7 +177,7 @@ const logSecurityEvent = async (event: SecurityEvent) => {
|
||||
#### 安全管線設定
|
||||
|
||||
```yaml
|
||||
# ✅ 正確:安全的 GitHub Actions 工作流程
|
||||
# PASS: 正確:安全的 GitHub Actions 工作流程
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
@@ -237,7 +237,7 @@ jobs:
|
||||
#### Cloudflare 安全設定
|
||||
|
||||
```typescript
|
||||
// ✅ 正確:帶安全標頭的 Cloudflare Workers
|
||||
// PASS: 正確:帶安全標頭的 Cloudflare Workers
|
||||
export default {
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const response = await fetch(request);
|
||||
@@ -281,7 +281,7 @@ export default {
|
||||
#### 自動備份
|
||||
|
||||
```terraform
|
||||
# ✅ 正確:自動 RDS 備份
|
||||
# PASS: 正確:自動 RDS 備份
|
||||
resource "aws_db_instance" "main" {
|
||||
allocated_storage = 20
|
||||
engine = "postgres"
|
||||
@@ -327,10 +327,10 @@ resource "aws_db_instance" "main" {
|
||||
### S3 Bucket 暴露
|
||||
|
||||
```bash
|
||||
# ❌ 錯誤:公開 bucket
|
||||
# FAIL: 錯誤:公開 bucket
|
||||
aws s3api put-bucket-acl --bucket my-bucket --acl public-read
|
||||
|
||||
# ✅ 正確:私有 bucket 並有特定存取
|
||||
# PASS: 正確:私有 bucket 並有特定存取
|
||||
aws s3api put-bucket-acl --bucket my-bucket --acl private
|
||||
aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
||||
```
|
||||
@@ -338,12 +338,12 @@ aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
||||
### RDS 公開存取
|
||||
|
||||
```terraform
|
||||
# ❌ 錯誤
|
||||
# FAIL: 錯誤
|
||||
resource "aws_db_instance" "bad" {
|
||||
publicly_accessible = true # 絕不這樣做!
|
||||
}
|
||||
|
||||
# ✅ 正確
|
||||
# PASS: 正確
|
||||
resource "aws_db_instance" "good" {
|
||||
publicly_accessible = false
|
||||
vpc_security_group_ids = [aws_security_group.db.id]
|
||||
|
||||
@@ -313,39 +313,39 @@ npm run test:coverage
|
||||
|
||||
## 常見測試錯誤避免
|
||||
|
||||
### ❌ 錯誤:測試實作細節
|
||||
### FAIL: 錯誤:測試實作細節
|
||||
```typescript
|
||||
// 不要測試內部狀態
|
||||
expect(component.state.count).toBe(5)
|
||||
```
|
||||
|
||||
### ✅ 正確:測試使用者可見行為
|
||||
### PASS: 正確:測試使用者可見行為
|
||||
```typescript
|
||||
// 測試使用者看到的內容
|
||||
expect(screen.getByText('Count: 5')).toBeInTheDocument()
|
||||
```
|
||||
|
||||
### ❌ 錯誤:脆弱的選擇器
|
||||
### FAIL: 錯誤:脆弱的選擇器
|
||||
```typescript
|
||||
// 容易壞掉
|
||||
await page.click('.css-class-xyz')
|
||||
```
|
||||
|
||||
### ✅ 正確:語意選擇器
|
||||
### PASS: 正確:語意選擇器
|
||||
```typescript
|
||||
// 對變更有彈性
|
||||
await page.click('button:has-text("Submit")')
|
||||
await page.click('[data-testid="submit-button"]')
|
||||
```
|
||||
|
||||
### ❌ 錯誤:無測試隔離
|
||||
### FAIL: 錯誤:無測試隔離
|
||||
```typescript
|
||||
// 測試互相依賴
|
||||
test('creates user', () => { /* ... */ })
|
||||
test('updates same user', () => { /* 依賴前一個測試 */ })
|
||||
```
|
||||
|
||||
### ✅ 正確:獨立測試
|
||||
### PASS: 正確:獨立測試
|
||||
```typescript
|
||||
// 每個測試設置自己的資料
|
||||
test('creates user', () => {
|
||||
|
||||
Reference in New Issue
Block a user