介绍
# Portable Tools - Cross-Device Development Methodology
构建适用于不同设备、命名方案和配置的工具的方法论。基于 OAuth 刷新器调试会议(2026-01-23)的经验。
## 核心原则
**永远不要假设你的设备是唯一的设备。**
你的本地设置只是众多可能配置中的一种。为通用情况构建,而不是特定实例。
---
## 编写代码前的三个问题
### 1. “设备之间有什么差异?”
在编写任何读取配置、数据或凭据的代码之前:
**询问:** - 文件路径?(macOS 对比 Linux,不同的主目录) - 账户名称?(user123 对比 default 对比 oauth) - 服务名称?(拼写/大小写的细微差异) - 数据结构?(不同的版本,不同的格式) - 环境?(不同的 shell,不同的可用工具)
**来自 OAuth 刷新器的示例:** - ❌ 假设:账户始终是 "claude" - ✅ 现实:可能是 "claude"、"Claude Code"、"default" 等。
**行动:** 列出变量,使其可配置或自动发现。
---
### 2. “我如何证明这有效?”
在声称成功之前:
**要求:** - 具体的 BEFORE 状态(精确值) - 具体的 AFTER 状态(精确值) - 证明它们不同(并排比较)
**来自 OAuth 刷新器的示例:** ``` BEFORE: - Access Token: POp5z1fi...eSN9VAAA - Expires: 1769189639000
AFTER: - Access Token: 01v0RrFG...eOE9QAA ✅ Different - Expires: 1769190268000 ✅ Extended ```
**行动:** 始终使用真实值显示数据转换。
---
### 3. “当它出故障时会发生什么?”
在推送到生产环境之前:
**测试:** - 错误的配置(故意破坏配置) - 缺失数据(移除预期字段) - 多个条目(歧义情况) - 边缘情况(空值、特殊字符)
**来自 OAuth 刷新器的示例:** - 使用 `keychain_account: "wrong-name"` 测试 → 降级应该有效 - 使用不完整的钥匙串数据测试 → 应该优雅地失败并给出有用的错误
**行动:** 测试失败模式,而不仅仅是快乐路径。
---
## 强制模式
### 模式 1:显式优于隐式
**❌ 错误:** ```bash # Ambiguous - returns first match security find-generic-password -s "Service" -w ```
**✅ 正确:** ```bash # Explicit - returns specific entry security find-generic-password -s "Service" -a "account" -w ```
**规则:** 如果命令可能有歧义,请使其显式化。
---
### 模式 2:使用前验证
**❌ 错误:** ```bash DATA=$(read_config) USE_VALUE="$DATA" # Hope it's valid ```
**✅ 正确:** ```bash DATA=$(read_config) if ! validate_structure "$DATA"; then error "Invalid data structure" fi USE_VALUE="$DATA" ```
**规则:** 永远不要假设数据具有预期的结构。
---
### 模式 3:降级链
**❌ 错误:** ```bash ACCOUNT="claude" # Hardcoded ```
**✅ 正确:** ```bash # Try configured → Try common → Error with help ACCOUNT="${CONFIG_ACCOUNT}" if ! has_data "$ACCOUNT"; then for fallback in "claude" "default" "oauth"; do if has_data "$fallback"; then ACCOUNT="$fallback" break fi done fi [[ -z "$ACCOUNT" ]] && error "No account found. Tried: ..." ```
**规则:** 为常见变体提供自动降级。
---
### 模式 4:有用的错误
**❌ 错误:** ```bash [[ -z "$TOKEN" ]] && error "No token" ```
**✅ 正确:** ```bash [[ -z "$TOKEN" ]] && error "No token found
Checked: - Config: $CONFIG_FILE - Field: $FIELD_NAME - Expected: { \"tokens\": { \"refresh\": \"...\" } }
Verify with: cat $CONFIG_FILE | jq '.tokens' " ```
**规则:** 错误消息应该帮助用户诊断和修复问题。
---
## 调试方法论(Patrick 的方法)
### 步骤 1:获取精确数据
**不要问:** “它坏了吗?” **问:** “你看到什么精确值?存在多少个条目?哪一个包含数据?”
**示例:** ```bash # Vague "Check keychain"
# Specific "Run: security find-generic-password -l 'Service' | grep 'acct'" "Tell me: 1. How many entries 2. Which has tokens 3. Last modified" ```
---
### 步骤 2:用具体示例证明
**不要说:** “它现在应该可以工作了” **展示:** “这是 BEFORE token (POp5z...),这是 AFTER (01v0R...),它们是不同的”
**模板:** ``` BEFORE: - Field1: <exact_value> - Field2: <exact_value>
AFTER: - Field1: <new_value> ✅ Changed - Field2: <new_value> ✅ Changed
PROOF: Values are different ```
---
### 步骤 3:立即考虑跨设备情况
**不要想:** “在我的机器上能跑” **想:** “如果他们的设置在 [X] 方面不同怎么办?”
**检查清单:** - [ ] 不同的账户名称? - [ ] 不同的文件路径? - [ ] 不同的工具/版本? - [ ] 不同的权限? - [ ] 不同的数据格式?
---
## 发布前检查清单
### 发现阶段 - [ ] 列出所有外部依赖(文件、命令、服务) - [ ] 记录每个依赖提供的内容 - [ ] 识别哪些部分可能在设备之间有所不同
### 实现阶段 - [ ] 使变体可配置(具有合理的默认值) - [ ] 为每个输入添加验证 - [ ] 为常见变体构建降级链 - [ ] 添加 `--dry-run` 或 `--test` 模式
### 测试阶段 - [ ] 使用正确的配置测试 → 应该能工作 - [ ] 使用错误的配置测试 → 应该降级或优雅地失败 - [ ] 使用缺失数据测试 → 应该给出有用的错误 - [ ] 使用多个条目测试 → 应该处理歧义
### 文档阶段 - [ ] 记录默认假设 - [ ] 记录如何验证本地设置 - [ ] 记录常见变体以及如何处理它们 - [ ] 包含数据流图 - [ ] 添加故障排除部分
---
## 真实示例:OAuth 刷新器
### 原始(已损坏) ```bash # Assumes single entry, no validation, no fallback KEYCHAIN_DATA=$(security find-generic-password -s "Service" -w) REFRESH_TOKEN=$(echo "$KEYCHAIN_DATA" | jq -r '.refreshToken') # Use token (hope it's valid) ```
**问题:** - 返回第一个字母顺序匹配的条目(错误的条目) - 没有验证(可能为空/格式错误) - 没有降级(如果账户名称不同则失败)
---
### 修复后(可移植) ```bash # Explicit account with validation and fallback validate_data() { echo "$1" | jq -e '.claudeAiOauth.refreshToken' > /dev/null 2>&1 }
# Try configured account DATA=$(security find-generic-password -s "$SERVICE" -a "$ACCOUNT" -w 2>&1) if validate_data "$DATA"; then log "✓ Using account: $ACCOUNT" else log "⚠ Trying fallback accounts..." for fallback in "claude" "Claude Code" "default"; do DATA=$(security find-generic-password -s "$SERVICE" -a "$fallback" -w 2>&1) if validate_data "$DATA"; then ACCOUNT="$fallback" log "✓ Found data in: $fallback" break fi done fi
[[ -z "$DATA" ]] || ! validate_data "$DATA" && error "No valid data found Tried accounts: $ACCOUNT, claude, Claude Code, default Verify with: security find-generic-password -l '$SERVICE'"
REFRESH_TOKEN=$(echo "$DATA" | jq -r '.claudeAiOauth.refreshToken') ```
**改进:** - ✅ 显式账户参数 - ✅ 验证数据结构 - ✅ 自动降级到常用名称 - ✅ 有用的错误并提供验证命令
---
## 常见反模式
### 反模式 1:“在我的机器上能跑” ```bash FILE="/Users/patrick/.config/app.json" # Hardcoded path ```
**修复:** 使用 `$HOME`,检测操作系统,或使其可配置
---
### 反模式 2:“希望它在” ```bash TOKEN=$(cat config.json | jq -r '.token') # What if .token doesn't exist? Script continues with empty value ```
**修复:** 使用前验证 ```bash TOKEN=$(cat config.json | jq -r '.token // empty') [[ -z "$TOKEN" ]] && error "No token in config" ```
---
### 反模式 3:“第一个匹配就是对的” ```bash # If multiple entries exist, which one? ENTRY=$(find_entry "service") ```
**修复:** 显式化或枚举所有 ```bash ENTRY=$(find_entry "service" "account") # Specific # OR ALL=$(find_all_entries "service") for entry in $ALL; do validate_and_use "$entry" done ```
---
### 反模式 4:“静默失败” ```bash process_data || true # Ignore errors ```
**修复:** 带着上下文大声失败 ```bash process_data || error "Failed to process Data: $DATA Expected: { ... } Check: command_to_verify" ```
---
## 与现有工作流集成
### 与 sprint-plan.md 添加到测试部分: ```markdown ## Cross-Device Testing - [ ] Test with different account names - [ ] Test with wrong config values - [ ] Test with missing data - [ ] Document fallback behavior ```
### 与 PRIVACY-CHECKLIST.md 发布前添加: ```markdown ## Portability Check - [ ] No hardcoded paths (use $HOME, detect OS) - [ ] No hardcoded names (use config or fallback) - [ ] Validation on all inputs - [ ] Helpful errors for common issues ```
### 与 skill-creator 构建新技能时: 1. 列出设备之间有什么差异 2. 使其可配置或自动发现 3. 使用错误的配置进行测试 4. 记录故障排除
---
## 快速参考卡
**编写代码前:** 1. 设备之间有什么差异? 2. 我如何证明这有效? 3. 当它出故障时会发生什么?
**强制模式:** - 显式优于隐式 - 使用前验证 - 降级链 - 有用的错误
**测试:** - 正确的配置 → 能工作 - 错误的配置 → 降级或有用的错误 - 缺失数据 → 清晰的诊断
**文档:** - 数据流图 - 常见变体 - 故障排除指南
---
## 成功标准
当一个工具满足以下条件时,它是**可移植的**:
1. ✅ 在不同的设备上无需修改即可工作 2. ✅ 自动发现设置中的常见变体 3. ✅ 使用可操作错误消息优雅地失败 4. ✅ 可以通过阅读错误输出来调试 5. ✅ 文档涵盖“如果我的设置不同怎么办”
**测试:** 把它给一个设置不同的人。如果他们需要问你问题,那么该工具还不可移植。
---
## 起源故事
这种方法论诞生于调试 OAuth 刷新器(2026-01-23)的过程: - 脚本读取了错误的钥匙串条目(未指定账户) - 假设存在单个条目(实际上存在多个) - 没有验证(使用了空数据) - 没有降级(在不同的账户名称上失败)
Patrick 的方法: 1. 询问精确数据(有多少个条目,哪个包含 token) 2. 要求证明(显示 BEFORE/AFTER token) 3. 考虑跨设备(如果命名不同怎么办?)
结果:工具从单设备/损坏变为通用/生产就绪。
**关键见解:** 错误不在逻辑中——而在假设中。
---
## 何时使用此技能
**使用场景:** - 构建读取系统配置的工具 - 使用钥匙串、凭据、环境变量 - 创建在多台机器上运行的脚本 - 将技能发布到 ClawdHub(其他人将使用它们)
**应用:** 1. 实施前:回答三个问题 2. 实施期间:使用强制模式 3. 测试前:运行发布前检查清单 4. 测试后:记录变体和故障排除
**记住:** 你的设备只是其中一种情况。为通用情况构建。