Vibe Coding 的安全风险与应对策略

AI 驱动的代码生成正在改变软件开发方式,但快速开发背后隐藏着机器规模的安全风险。 Vibe Coding 的兴起 Vibe Coding 是 OpenAI 联合创始人 Andrej Karpathy 在今年早些时候创造的术语,描述了一种全新的开发模式:开发者不再逐行编写代码,而是通过提示词指导 AI 生成代码,自己退居到更具战略性的”导演”角色。 这种方式比主流的 AI 辅助编程更进一步。GitHub Copilot 和 Claude 等工具让开发者能够以”爆发式”的速度编写代码,AI 负责处理细节。对企业而言,吸引力显而易见:开发者可以快速实验想法,团队生产力可能大幅提升。 Checkmarx 的全球研究显示,50% 的受访者使用某种形式的 AI 编码助手,三分之一的组织通过这种方式生成多达 60% 的代码。AI 的价值主张太强,无法忽视。 当 Vibe 出错时 LLM 的概率特性 任何使用过大语言模型的人都知道,它们的概率特性就像老虎机——用同一个提示词拉五次杠杆,会得到五个不同的结果。可能中大奖,可能什么都没有,也可能得到介于两者之间、勉强可用的东西。 常见问题 将这种特性应用到编码中,各种问题都会出现: 幻觉 API:AI 工具可能生成不存在的 API 过时依赖:插入已弃用的库 脆弱代码:生成脆弱或不透明的代码,悄悄侵蚀可维护性 架构破坏:AI 代理可能通过非授权接口粘合组件,绕过模块化设计 意外删除:关键代码可能因意外删除和其他错误而丢失 一个在网上流传的警示故事中,一个失控的 AI 代理在代码冻结期间删除了整个数据库。 供应链风险 风险还延伸到供应链:AI 工具可能引入未经审查的依赖项、包含已知漏洞的包,甚至完全幻觉它们的存在。 如果这些问题得不到解决,组织面临将更多低质量代码引入环境的风险,包括完整的架构和运营威胁。更令人担忧的是,研究发现只有 18% 的组织目前制定了 AI 使用政策。 “循环中的谎言”(LITL) 即使开发者有人工干预(HITL)步骤来验证 AI 创建的代码,Checkmarx 研究团队最近发现了一种被称为”循环中的谎言”(lies in the loop, LITL)的技术,可以欺骗 Claude Code 等 AI 编码助手执行更危险的活动,同时在人工监督下显得安全。 风险预防从架构和思维开始 模块化架构 缓解 AI 生成代码的风险要在漏洞引入之前就开始。模块化架构至关重要:当有明确定义的边界和授权 API 时,一个幻觉函数或失控依赖的潜在破坏范围会自然受到限制。 这些原则并不新鲜,但在 AI 代理可能不尊重抽象的环境中变得更加紧迫,除非明确引导。 开发者思维转变 开发者思维同样重要。Vibe Coding 将开发者定位为系统架构师,而不是逐行作者——编写提示词、审查输出、决定发布什么。 这种转变需要技能提升。研究发现,理解架构和提示策略的开发者比那些将传统工作流应用于生成工具的开发者有效得多。 当前状态 在目前状态下,Vibe 方法更适合实验和原型设计,而不是将完成的代码投入生产环境。组织应该以此为出发点进行探索。至关重要的是,一切都要经过同样严格的安全流程,使用左移思维尽早引入这些检查。 预防现在取决于智能设计和一个知道如何与 AI 协作(而不仅仅是围绕它工作)的团队。 快速反馈循环与 DevSecOps 实时检测与修正 在 AI 驱动的开发中,错误可能快速引入,因此必须同样快速地发现和修复。依赖传统检查点是不够的。检测和修正需要嵌入到整个开发工作流程中。 这意味着将实时安全扫描直接集成到开发者环境中——在 IDE 和 pull request 中,而不仅仅是 CI/CD 管道中。 AI 驱动的安全代理 安全和质量需要人类专业知识,新兴的 AI 驱动安全代理可以支持开发者大规模应用这一点——标记问题、建议修复,甚至在代码离开本地环境之前指导修复。 DevSecOps 的角色 这正是 DevSecOps 大显身手的地方。这些团队处于独特位置,可以闭合创建和修正之间的循环,嵌入防护栏,加速反馈,构建预期 AI 波动性(而不仅仅是对其做出反应)的系统。 当风险被及早并在上下文中检测到时,它是可管理的。当在高速、AI 生成的系统中积累时,遏制它的难度和成本会高得多。 总结 Vibe Coding 目前是一个有争议的话题,被一些团队接受,被另一些团队视为一时风尚而拒绝。无论它是否成为软件开发的新基准,组织都必须为 AI 时代准备好严格的流程和安全防护。 关键要点: 模块化架构是控制 AI 代码风险的基础 开发者需要从编码者转变为架构师思维 实时安全扫描必须嵌入开发环境 DevSecOps 在 AI 时代的重要性空前 目前 Vibe Coding 更适合原型而非生产环境 原文链接: The risks of vibe coding - Business Reporter 作者: Eran Kinsbruner, Checkmarx VP Portfolio Marketing

2025/11/2
articleCard.readMore

Vibe Coding 最佳实践:10 个让开发效率提升 10 倍的关键技巧

本文整理 Vibe Coding(AI 辅助编程)的 10 个核心最佳实践,帮助提升开发效率和代码质量。 1. 根据任务选择模型 Claude Haiku 4.5:速度最快、成本最低($0.25/MTok 输入,$1.25/MTok 输出),适合简单快速任务(代码格式化、简单 bug 修复、基础代码补全、文档注释生成)。响应速度快,适合高频调用场景。 Claude Sonnet 4.5:日常开发首选,用于 70-80% 编程任务(代码生成、重构、测试),性价比最高($3/MTok 输入,$15/MTok 输出)。 Claude Opus 4.1:复杂推理任务(多步骤工作流、架构决策),价格是 Sonnet 5 倍,支持 7 小时以上自主编程。 OpenAI Codex / GPT-4:擅长代码补全和快速生成,GitHub Copilot 基于此技术。适合 IDE 内实时代码提示、函数级补全、单元测试生成。 决策框架: 简单快速任务 → Claude Haiku 4.5 实时代码补全 → Codex 日常代码生成、重构 → Claude Sonnet 4.5 复杂架构设计、多文件重构 → Claude Opus 4.1 2. 使用四要素 Prompt 框架 核心框架: 上下文/角色:设定专业背景(”你是精通 Kotlin 协程的 Android 性能优化专家”) 指令:清晰的单一任务命令(”重构此 ViewModel 以减少数据库查询次数”) 内容:使用代码块标记实际代码 格式:明确输出结构(”提供重构后的代码并添加注释解释变更”) 实用模式: 逐步说明:”创建 Kotlin 函数:1. 接收用户 ID 列表;2. 从 Room 获取数据;3. 过滤活跃用户;4. 返回 Flow 并处理异常” 示例驱动:”参考以下 ViewModel 写法,转换这个 Activity” 角色扮演:”作为 Android 安全专家,审查此登录代码,重点关注 SharedPreferences 加密、网络请求安全、WebView 配置” 避免: 模糊请求(”让这个更好”) 一次多个无关任务 缺少错误堆栈的调试请求 3. 善用代码上下文和文件引用 核心问题:AI 不了解项目结构,生成代码可能与项目风格不一致。 提供上下文的方法: 引用文件: 1 请参考 UserRepository.kt 的写法,为 ProductRepository 实现相同的缓存逻辑 粘贴关键代码: 1 2 3 4 5 // 现有的 BaseViewModel 实现 abstract class BaseViewModel : ViewModel() { protected val _loading = MutableStateFlow(false) } // 请按此模式实现 UserViewModel 说明架构: 1 2 3 4 5 项目使用 MVVM 架构: - data/ 层:Repository + DataSource - domain/ 层:UseCase + Model - presentation/ 层:ViewModel + Activity/Fragment 使用 Hilt、Room、Retrofit 使用 @文件 语法: 1 2 @MainActivity.kt 这个 Activity 的内存泄漏在哪里? @app/build.gradle 添加 Coil 图片加载库 首次对话说明技术栈和架构,涉及多文件时列出文件名,生成代码时提供参考示例。 4. 针对复杂问题开启扩展思考模式 适用场景:复杂算法(图片压缩、列表优化)、不明确原因的 bug(ANR、内存泄漏)、架构决策(MVVM vs MVI)、跨文件重构、性能优化。 不推荐:简单补全、语法修复、基本 CRUD、简单 UI 布局。 Claude Code 魔法词: think:4,000 token think hard / megathink:10,000 token think harder / ultrathink:31,999 token 性能提升:SWE-bench Verified 从 62.3% 提升至 70.3%,数学问题达 96.2%。 从最小预算(1,024 token)开始,根据问题复杂度逐步增加。 5. 采用小步迭代开发 黄金法则:一次生成太多代码会导致混乱和 bug,使用最小有意义增量。 增量流程:定义最小增量 → 编写失败测试 → AI 编写通过测试的代码 → 立即运行测试 → 失败则让 AI 诊断修复 → 重复。 架构先行:编码前先绘制模块图(Repository-ViewModel-View)、定义数据流(LiveData/StateFlow)、确定组件职责。 多轮精炼:第一轮生成基本结构 → 第二轮添加错误处理 → 第三轮优化性能 → 第四轮添加完整文档。每一轮都小而专注、可测试。 6. 审查代码并建立测试防线 核心原则:永远不要盲目接受 AI 输出。GitClear 研究发现粗心使用 AI 导致 bug 增加 41%。 重点审查:潜在 bug、安全漏洞(未加密 SharedPreferences、不安全 WebView、Intent 劫持)、性能瓶颈(主线程阻塞、内存泄漏、过度绘制)、缺失错误处理、生命周期管理问题。 测试生成:使用 AI 生成单元测试和 UI 测试,但必须人工验证测试是否真实有效、边界情况有意义(空列表、网络错误、权限拒绝)、使用合适框架(JUnit、Mockito、Espresso、Robolectric)。 覆盖率目标:ViewModel/Repository 层、复杂业务逻辑、数据转换工具类追求 80%+ 代码覆盖率。 7. 与版本控制系统深度集成 核心原则:Git 是安全网,每个 AI 生成的代码都必须提交,便于快速回退错误修改。 快速回退命令: 1 2 3 4 git checkout . # 放弃所有修改 git checkout -- file.kt # 放弃指定文件修改 git reset HEAD <file> # 回退暂存区 git reset --hard HEAD^ # 完全回退到上一次提交 原子提交:每次提交一个逻辑变更,使用祈使语气,消息正文解释原因。 分支管理:使用清晰命名(feat/add-retrofit-api、fix/memory-leak-viewmodel)、所有 AI 实验都使用分支、出问题直接删除分支。 8. 建立项目规则和指南文件 规则文件:创建 .editorconfig、detekt.yml 或 docs/coding-standards.md,定义编码规范(优先使用 Kotlin、Activity/Fragment 最大 500 行、使用 ViewBinding)、测试要求(80% 覆盖率、JUnit 和 Espresso、ViewModel 必须有单元测试)、文档标准(public 方法使用 KDoc、每个模块需 README)、安全策略(永不硬编码 API 密钥、使用 EncryptedSharedPreferences)。 Android 常用配置: .editorconfig - 统一代码格式 detekt.yml - Kotlin 代码质量检查 lint.xml - Android Lint 配置 docs/android-conventions.md - 团队开发规范 配置方法:Cursor IDE 在设置中添加用户规则,Claude Projects 在自定义指令中添加规则。 核心收益:AI 自动遵循项目规范,团队代码生成一致,保持质量和可维护性。 9. 及时会话管理 三种策略: 清理会话(Clear):任务切换或 AI 出现混乱时使用 /clear 或 Cmd/Ctrl + Shift + N。 新建会话(New Chat):开始完全不同的任务时,避免上下文混淆。 压缩会话(Compact):长会话超过 50 轮对话时使用 /compact,保留关键信息,释放上下文空间。 时长建议: 短任务:15-30 分钟,10-20 轮 中等任务:1-2 小时,30-50 轮 复杂项目:单次不超过 3-4 小时 超过 50-70 轮对话后性能明显下降,及时管理会话。 10. 使用 MCP 扩展 AI 能力 什么是 MCP:Model Context Protocol 让 AI 直接执行 ADB 命令、查询设备状态,无需手动复制日志。 Android ADB MCP Server 创建 adb-mcp-server.js: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #!/usr/bin/env node const { Server } = require('@modelcontextprotocol/sdk/server/index.js'); const { exec } = require('child_process'); const { promisify } = require('util'); const execPromise = promisify(exec); const server = new Server({ name: 'adb-server', version: '1.0.0' }); server.tool('adb-logcat', 'Get device logs', async ({ lines = 100 }) => { const { stdout } = await execPromise(`adb logcat -d -t ${lines}`); return { content: stdout }; }); server.tool('adb-meminfo', 'Get memory usage', async ({ packageName }) => { const { stdout } = await execPromise(`adb shell dumpsys meminfo ${packageName}`); return { content: stdout }; }); server.tool('adb-install', 'Install APK', async ({ apkPath }) => { const { stdout } = await execPromise(`adb install -r ${apkPath}`); return { content: stdout }; }); server.start(); 配置 Claude Desktop(~/Library/Application Support/Claude/claude_desktop_config.json): 1 2 3 4 5 6 7 8 { "mcpServers": { "adb": { "command": "node", "args": ["/path/to/adb-mcp-server.js"] } } } 实战场景 分析崩溃: 1 2 3 用户:应用启动时崩溃 AI:[调用 adb-logcat] 发现 NullPointerException in MainActivity:45 user 对象为 null,建议使用 safe call (?.) 性能排查: 1 2 3 用户:列表滑动很卡 AI:[调用 adb-meminfo] 内存 450MB,ImageLoader 持有 Activity 引用 建议使用 WeakReference 和 Glide 生命周期管理 配置建议 安全限制:添加包名白名单,避免误操作。 错误处理:捕获异常,提示检查 USB 调试。 性能优化:限制 logcat 行数(默认 100),使用 -d 参数。 总结 掌握这十个最佳实践后,原本需要 3 天完成的功能模块可以压缩到半小时。关键在于将 AI 视为超级助手而非普通工具,快速试错、频繁提交、大胆回退,让看似不可能的开发效率成为现实。

2025/10/15
articleCard.readMore

一看就会,为 AI 编程 Agent 撸一个 MCP 服务

通过 MCP (Model Context Protocol) 让 AI 助手直接调用 ADB 命令操作 Android 设备,实现日志查看、应用安装、性能分析等自动化操作。 MCP 协议说明 MCP 是 Anthropic 推出的开放协议,用于连接 AI 助手与外部工具。MCP Server 将特定工具包装成标准化接口,让 AI 能够理解和调用。 架构如下: 1 Claude Desktop/API → MCP Server → ADB Commands → Android Device 实现步骤 初始化项目 1 2 3 4 mkdir adb_mcp cd adb_mcp npm init -y npm install @modelcontextprotocol/sdk 配置 package.json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 { "name": "adb-mcp-server", "version": "1.0.0", "description": "MCP Server for Android Debug Bridge (ADB) operations", "main": "adb-mcp-server.js", "bin": { "adb-mcp-server": "./adb-mcp-server.js" }, "scripts": { "start": "node adb-mcp-server.js" }, "keywords": ["mcp", "adb", "android", "debug"], "author": "", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0" } } 核心代码实现 创建 adb-mcp-server.js: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 #!/usr/bin/env node const { Server } = require('@modelcontextprotocol/sdk/server/index.js'); const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js'); const { CallToolRequestSchema, ListToolsRequestSchema } = require('@modelcontextprotocol/sdk/types.js'); const { exec } = require('child_process'); const { promisify } = require('util'); const execPromise = promisify(exec); // 创建 MCP Server 实例 const server = new Server( { name: 'adb-server', version: '1.0.0' }, { capabilities: { tools: {} } } ); // 定义工具列表 const tools = [ { name: 'adb-logcat', description: 'Get Android device logs', inputSchema: { type: 'object', properties: { lines: { type: 'number', description: 'Number of log lines to retrieve', default: 100 } } } }, { name: 'adb-devices', description: 'List connected Android devices', inputSchema: { type: 'object', properties: {} } }, { name: 'adb-install', description: 'Install APK on connected device', inputSchema: { type: 'object', properties: { apkPath: { type: 'string', description: 'Path to the APK file to install' } }, required: ['apkPath'] } }, { name: 'adb-app-info', description: 'Get app package information', inputSchema: { type: 'object', properties: { packageName: { type: 'string', description: 'Android package name (e.g., com.example.app)' } }, required: ['packageName'] } }, { name: 'adb-meminfo', description: 'Get app memory usage information', inputSchema: { type: 'object', properties: { packageName: { type: 'string', description: 'Android package name (e.g., com.example.app)' } }, required: ['packageName'] } }, { name: 'adb-clear-data', description: 'Clear app data and cache', inputSchema: { type: 'object', properties: { packageName: { type: 'string', description: 'Android package name (e.g., com.example.app)' } }, required: ['packageName'] } } ]; // 处理工具列表请求 server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); // 处理工具执行请求 server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { let result; switch (name) { case 'adb-logcat': { const lines = args.lines || 100; const { stdout } = await execPromise(`adb logcat -d -t ${lines}`); result = stdout; break; } case 'adb-devices': { const { stdout } = await execPromise('adb devices -l'); result = stdout; break; } case 'adb-install': { if (!args.apkPath) { throw new Error('apkPath is required'); } const { stdout } = await execPromise(`adb install -r "${args.apkPath}"`); result = stdout; break; } case 'adb-app-info': { if (!args.packageName) { throw new Error('packageName is required'); } const { stdout } = await execPromise(`adb shell dumpsys package ${args.packageName}`); result = stdout; break; } case 'adb-meminfo': { if (!args.packageName) { throw new Error('packageName is required'); } const { stdout } = await execPromise(`adb shell dumpsys meminfo ${args.packageName}`); result = stdout; break; } case 'adb-clear-data': { if (!args.packageName) { throw new Error('packageName is required'); } const { stdout } = await execPromise(`adb shell pm clear ${args.packageName}`); result = stdout; break; } default: throw new Error(`Unknown tool: ${name}`); } return { content: [ { type: 'text', text: result } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error executing ${name}: ${error.message}` } ], isError: true }; } }); // 启动服务器 async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('ADB MCP Server running on stdio'); } main().catch((error) => { console.error('Server error:', error); process.exit(1); }); 赋予执行权限: 1 chmod +x adb-mcp-server.js 关键实现说明 Server 初始化 1 2 3 4 const server = new Server( { name: 'adb-server', version: '1.0.0' }, { capabilities: { tools: {} } } ); 声明服务器名称、版本和支持的能力类型。 工具定义 每个工具包含三个部分: name: 唯一标识符 description: 功能描述,AI 根据此判断调用时机 inputSchema: JSON Schema 格式的参数定义 示例: 1 2 3 4 5 6 7 8 9 10 11 { name: 'adb-install', description: 'Install APK on connected device', inputSchema: { type: 'object', properties: { apkPath: { type: 'string', description: 'Path to the APK file' } }, required: ['apkPath'] } } 请求处理 MCP 定义两种请求类型: ListToolsRequest - 列出所有可用工具: 1 2 3 server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); CallToolRequest - 执行具体工具: 1 2 3 4 server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; // 根据 name 执行相应 ADB 命令 }); 命令执行 使用 child_process 执行 ADB 命令: 1 const { stdout } = await execPromise(`adb devices -l`); 配置方式 Claude Desktop 配置 编辑 ~/Library/Application Support/Claude/claude_desktop_config.json: 1 2 3 4 5 6 7 8 { "mcpServers": { "adb": { "command": "node", "args": ["/Users/你的用户名/Documents/self_host/adb_mcp/adb-mcp-server.js"] } } } 配置完成后重启 Claude Desktop。 Claude Code CLI 配置 使用 Claude Code 命令行工具添加 MCP Server: 1 claude -p mcp add adb node /Users/xxxx/Documents/sxxx/adb_mcp/adb-mcp-server.js 参数说明: adb: MCP Server 名称 node: 运行命令 最后是 adb-mcp-server.js 的完整路径 Gemini CLI 配置 编辑 Gemini CLI 配置文件 ~/.gemini/mcp_config.json: 1 2 3 4 5 6 7 8 { "mcpServers": { "adb": { "command": "node", "args": ["/Users/xxxx/Documents/sxxx/adb_mcp/adb-mcp-server.js"] } } } 注:Gemini CLI 的 MCP 配置可能因版本而异,建议以官方文档为准。 Copilot CLI 配置 编辑 Copilot CLI 配置文件 ~/.github-copilot/mcp_servers.json: 1 2 3 4 5 6 { "adb": { "command": "node", "args": ["/Users/xxxx/Documents/sxxx/adb_mcp/adb-mcp-server.js"] } } 注:Copilot CLI 的 MCP 配置可能因版本而异,建议以官方文档为准。 使用示例 在 Claude 中直接使用自然语言: 1 查看连接的 Android 设备 1 获取最近 50 行 logcat 日志 1 查看 com.android.chrome 的内存使用情况 1 清除 com.example.app 的数据 Claude 会自动调用对应的 MCP 工具执行操作。 应用场景 日志分析 传统方式: 1 2 adb logcat -d > log.txt # 手动搜索错误信息 使用 MCP: 1 获取最近的 logcat 日志,找出所有 ERROR 级别信息 Claude 自动执行并分析结果。 性能监控 传统方式: 1 2 adb shell dumpsys meminfo com.example.app # 手动分析输出数据 使用 MCP: 1 查看 com.example.app 的内存使用,分析是否有内存泄漏 批量操作 1 列出所有连接的设备,在每个设备上安装 /path/to/app.apk Claude 自动处理设备列表和批量安装。 扩展功能 添加截图 1 2 3 4 5 6 7 8 9 10 11 { name: 'adb-screenshot', description: 'Take a screenshot', inputSchema: { type: 'object', properties: { savePath: { type: 'string', description: 'Path to save screenshot' } }, required: ['savePath'] } } 执行命令: 1 await execPromise(`adb exec-out screencap -p > ${args.savePath}`); 添加性能监控 1 2 3 4 5 { name: 'adb-top', description: 'Get CPU usage', inputSchema: { type: 'object', properties: {} } } 添加文件传输 1 2 3 4 5 6 7 8 9 10 11 12 { name: 'adb-push', description: 'Push file to device', inputSchema: { type: 'object', properties: { localPath: { type: 'string' }, remotePath: { type: 'string' } }, required: ['localPath', 'remotePath'] } } 注意事项 权限检查 确保 ADB 已添加到系统 PATH: 1 2 which adb adb version 设备授权 使用前确认设备已连接并授权: 1 adb devices 如果显示 unauthorized,需在设备上确认 USB 调试授权。 错误处理 生产环境应添加: 详细日志记录 设备断开连接处理 命令超时机制 安全性 避免在不信任的环境使用 注意 APK 路径注入风险 考虑添加命令白名单 参考资源 MCP 官方文档 MCP SDK GitHub ADB 官方文档

2025/10/15
articleCard.readMore

同样是 Sonnet 4.5,为何 CLI 工具差距这么大

最近使用 Claude Code CLI 和 GitHub Copilot CLI 时发现,虽然两者都使用 Claude Sonnet 4.5 模型,但 Claude Code 明显更智能。本文记录性能差异的技术原因。 核心问题 同模型不等于同性能。Claude Sonnet 4.5 原生支持 200K tokens 上下文和 Extended Thinking,但 Copilot CLI 通过中间层大幅限制了这些能力。 Copilot CLI 的三大限制 1. 上下文窗口严重缩水 Claude Sonnet 4.5 原生能力: 标准:200K tokens 长上下文版本:1M tokens Copilot CLI 实际限制: 约 8K tokens 上下文窗口 官方未明确公布,但用户实测约为此值 实际影响: 1 2 3 4 5 6 7 8 9 10 11 # 场景:分析涉及 10 个文件的代码库 # Claude Code CLI (200K 上下文) # ✓ 可同时加载多个相关文件 # ✓ 保持完整的代码关系理解 # ✓ 前后一致的分析结果 # Copilot CLI (8K 上下文) # ✗ 只能保持 1-2 个文件 # ✗ 频繁遗忘先前分析内容 # ✗ 需要反复重新读取 8K tokens 约等于 6000 英文单词 或 1500 行代码。Copilot CLI 的小窗口导致频繁的上下文切换和信息丢失。 2. Extended Thinking 功能完全缺失 什么是 Extended Thinking: 允许模型进行深度推理,配置 1K-64K tokens 的”思考预算”,在复杂任务中显著提升表现。 Claude Code CLI 配置示例: 1 2 3 4 5 6 7 { "model": "claude-sonnet-4-5-20250929", "thinking": { "type": "enabled", "budget_tokens": 10000 } } Copilot CLI: 完全不支持此配置,无法启用 Extended Thinking。这是两者智能表现差异的关键原因。 3. 资源配额与超时策略 Claude Code CLI: 按 token 计费($3/百万输入,$15/百万输出) 允许长时间运行 支持检查点功能,可保存进度 Copilot CLI: Premium Request 配额制(Pro 300 次/月) 隐性”思考预算”限制 超时中断机制 比喻: Claude Code 设计用于 跑马拉松(长时间、多步骤任务),Copilot CLI 只能 跑百米(快速交互)。 架构差异 Claude Code CLI:直接访问 1 用户 → Anthropic API → Claude Sonnet 4.5 特点: 完整的 200K tokens 上下文 支持 Extended Thinking 完全参数控制 并行工具调用 无功能限制 代价: 使用 grep-only 检索(无语义索引) 大量顺序工具调用 速度慢 4-5 倍 Copilot CLI:中间层架构 1 用户 → GitHub 编排层 → Anthropic API → Claude Sonnet 4.5 中间层作用: 模型路由和切换 成本控制和配额管理 上下文窗口限制(8K) 屏蔽高级功能(Extended Thinking) GitHub 生态集成 优点: 多模型选择 GitHub 深度集成 相对稳定 代价: 模型能力被”阉割” 上下文限制严重 复杂任务表现差 实测性能对比 速度差异 重构 React 前端任务(约 15 个文件): Claude Code CLI: 18 分 20 秒 Claude Chat 手动: 4 分 30 秒 Copilot CLI: 约 90 分钟(18 分 × 5) 用户反馈:Copilot CLI 比 Claude Code 慢 5 倍以上。 大文件处理 Claude Code 限制: 1 2 # 单文件读取限制 25K tokens Error: File content (28375 tokens) exceeds maximum allowed tokens (25000) 需要使用 offset 和 limit 分块读取,导致反复工具调用。 Copilot CLI 问题: 8K tokens 窗口导致频繁分块 1000 行文件经常卡顿 有时卡死 30 分钟后超时 为什么 Claude Code “更智能” 1. 全局视野 vs 局部视野 Claude Code: 200K tokens 上下文窗口 可同时保持多个文件内容 理解代码全局关系 Copilot CLI: 8K tokens 上下文窗口 只能保持少量文件 频繁遗忘先前内容 2. 深度思考 vs 快速响应 Claude Code: Extended Thinking 允许模型”想”得更久 可以进行复杂推理 适合多步骤任务 Copilot CLI: 无 Extended Thinking 思考预算受限 倾向快速给出结果 3. 马拉松 vs 百米 Claude Code: 设计用于长时间运行 可处理复杂、多步骤任务 允许大量 token 消耗 Copilot CLI: 为快速交互优化 超出一定时限就中断 控制成本和资源 稳定性问题 Copilot CLI GitHub 社区反馈: Claude Sonnet 4 在约 5 个提示后停止 频繁 “I’m sorry but there was an error” 上下文突然丢失 官方承认是”已知的服务器端问题”。 Claude Code 使用 31% 配额时被提前限制 陷入”无限压缩循环” 读取大文件时崩溃 优化建议 Claude Code 使用语义索引: 1 2 # 安装 MCP 服务器(如 Serena MCP) # 替代低效 grep 检索 主动管理上下文: 1 2 /clear # 清理上下文 /compact # 压缩上下文 维护 CLAUDE.md: 项目规范 禁止目录 常用命令 Copilot CLI 监控配额: 1 /usage # 查看使用情况 按任务选模型: 复杂任务:Claude Sonnet 4.5 简单任务:Haiku 避免大任务接近限制时启动 总结 两者差异的根本原因: 维度 Claude Code CLI Copilot CLI 上下文窗口 200K tokens ~8K tokens Extended Thinking ✓ 支持 ✗ 不支持 资源策略 马拉松 百米 架构 直接访问 中间层限制 适用场景 复杂重构 快速迭代 Claude Code 提供完整模型能力但速度慢,像让模型”看全局、想得久”。 Copilot CLI 功能受限但集成好,像让模型”看局部、快速答”。 用户反馈的”8K tokens 限制”并非误解,而是 Copilot CLI 的真实约束。这个限制加上 Extended Thinking 缺失,是智能表现差异的核心原因。 实际使用中,许多开发者两者并用:Claude Code 处理复杂任务,Copilot CLI 处理快速交互。

2025/10/13
articleCard.readMore

定位 Android 权限声明来源

开发中经常需要排查某个权限是由哪个依赖库引入的,本文记录通过 Gradle daemon 日志快速定位权限来源的方法。 查询方法 使用以下命令在 Gradle daemon 日志中搜索权限声明: 1 grep -n -C 2 "android.permission.INTERNET" --include="*.out.log" -R ~/.gradle/daemon/ . 参数说明 -n:显示行号 -C 2:显示匹配行前后各 2 行上下文(关于 grep 上下文参数的详细用法见这篇文章) --include="*.out.log":只搜索 .out.log 文件 -R:递归搜索目录 ~/.gradle/daemon/:Gradle daemon 日志目录 daemon 日志文件说明 什么是 daemon*.out.log Gradle daemon 是 Gradle 构建系统的后台进程,用于加速构建过程。daemon-*.out.log 文件记录了 daemon 进程的详细输出信息,包括: 依赖解析过程:库的下载、合并信息 Manifest 合并日志:权限、组件的合并来源 构建任务执行:编译、打包等任务的详细输出 错误堆栈信息:构建失败时的完整日志 文件位置 1 2 3 4 5 6 7 ~/.gradle/daemon/ ├── 5.4.1/ │ ├── daemon-77407.out.log │ └── daemon-77408.out.log ├── 7.0.2/ │ └── daemon-88901.out.log └── ... 每个 Gradle 版本对应一个目录,每次 daemon 启动会生成新的日志文件,文件名中的数字为进程 ID。 结果分析 查询结果示例 1 2 /daemon/5.4.1/daemon-77407.out.log:132336:Merging uses-permission#android.permission.INTERNET with lower [net.butterflytv.utils:rtmp-client:3.0.1] AndroidManifest.xml:11:5-67 信息解读 从日志可以看出: 权限名称:android.permission.INTERNET 来源依赖:net.butterflytv.utils:rtmp-client:3.0.1 声明位置:该依赖的 AndroidManifest.xml 第 11 行 日志文件:daemon-77407.out.log 第 132336 行 处理方式 使用 <uses-permission tools:node="remove"> 在主 Manifest 中显式移除。 注意事项 daemon 日志会随着构建次数增多而变大,定期清理 ~/.gradle/daemon/ 目录 不同 Gradle 版本的日志格式可能略有差异 查询结果可能包含多个匹配项,需根据依赖关系判断实际来源 延伸阅读 Gradle Daemon 官方文档 Android Manifest 合并机制

2025/10/12
articleCard.readMore

Android 升级 targetSDK 35 解决 namespace 问题

升级 Android targetSDK 至 35 并使用 Gradle 8.0+ 后,遇到了第三方库 namespace 配置问题。 错误信息 1 2 3 4 Execution failed for task ':react-native-inappbrowser:processDebugManifest'. > A failure occurred while executing com.android.build.gradle.tasks.ProcessLibraryManifest$ProcessLibWorkAction > Setting the namespace via the package attribute in the source AndroidManifest.xml is no longer supported. Recommendation: remove package="com.proyecto26.inappbrowser" from the source AndroidManifest.xml. 或者类似错误: 1 2 3 4 5 Namespace not specified. Please specify a namespace in the module's build.gradle file like so: android { namespace 'com.example.namespace' } 原因分析 Android Gradle Plugin 8.0+ 不再支持在 AndroidManifest.xml 中通过 package 属性设置 namespace,要求在 build.gradle 中显式声明。升级 targetSDK 至 35 需要使用 Gradle 8.0+,但很多第三方库(如 react-native-inappbrowser、appcenter-analytics 等)尚未更新配置,导致构建失败。 解决方案 在项目根目录的 android/build.gradle 文件中添加以下代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 allprojects { repositories { google() mavenCentral() // 如果使用 Detox 测试框架,添加此配置 maven { url("$rootDir/../node_modules/detox/Detox-android") } } subprojects { afterEvaluate { project -> if (project.hasProperty('android')) { project.android { // 自动设置 namespace if (namespace == null || namespace.isEmpty()) { def defaultNamespace = project.group.toString().replace('.', '_') namespace = defaultNamespace } // 启用 buildConfig buildFeatures { buildConfig = true } } // 自动修复 namespace 和清理 AndroidManifest.xml project.tasks.register("fixManifestsAndNamespace") { doLast { // 1. 从 AndroidManifest.xml 提取 package 并添加到 build.gradle def buildGradleFile = file("${project.projectDir}/build.gradle") if (buildGradleFile.exists()) { def buildGradleContent = buildGradleFile.getText('UTF-8') def manifestFile = file("${project.projectDir}/src/main/AndroidManifest.xml") if (manifestFile.exists()) { def manifestContent = manifestFile.getText('UTF-8') def packageName = manifestContent.find(/package="([^"]+)"/) { match, p -> p } if (packageName && !buildGradleContent.contains("namespace")) { println "Setting namespace in ${buildGradleFile}" buildGradleContent = buildGradleContent.replaceFirst( /android\s*\{/, "android {\n namespace '${packageName}'" ) buildGradleFile.write(buildGradleContent, 'UTF-8') } } } // 2. 移除 AndroidManifest.xml 中的 package 属性 def manifests = fileTree(dir: project.projectDir, includes: ['**/AndroidManifest.xml']) manifests.each { File manifestFile -> def manifestContent = manifestFile.getText('UTF-8') if (manifestContent.contains('package=')) { println "Removing package attribute from ${manifestFile}" manifestContent = manifestContent.replaceAll(/package="[^"]*"/, '') manifestFile.write(manifestContent, 'UTF-8') } } } } // 在构建前自动执行修复 project.tasks.matching { it.name.startsWith("preBuild") }.all { dependsOn project.tasks.named("fixManifestsAndNamespace") } } } } } 说明 工作原理 此方案包含三个层次的处理: 自动设置 namespace:如果子项目未配置 namespace,自动使用 project.group 并将点号替换为下划线作为 namespace 启用 buildConfig:自动为所有子项目启用 buildConfig 特性 自动迁移配置: 从 AndroidManifest.xml 中提取 package 属性 将其写入对应的 build.gradle 作为 namespace 移除 AndroidManifest.xml 中的 package 属性 这个 task 在每次构建前(preBuild)自动执行,确保所有第三方库都符合 Gradle 8.0+ 的要求。 适用场景 React Native 项目升级 targetSDK 35 Flutter 项目升级 targetSDK 35 使用 Detox 测试框架的项目 原生 Android 项目使用旧版第三方库 任何遇到 “namespace not specified” 或 “package attribute not supported” 错误的场景 注意事项 此方案会自动修改第三方库的 build.gradle 和 AndroidManifest.xml 文件 修改仅在 node_modules 中生效,不影响源码仓库 建议在 CI/CD 中首次构建后检查修改是否正确 如果某些库已经声明了 namespace,不会被覆盖 验证 执行以下命令重新构建项目: 1 2 3 cd android ./gradlew clean ./gradlew assembleDebug 或在 React Native 项目中: 1 npx react-native run-android 构建过程中会看到类似输出: 1 2 Setting namespace in /path/to/project/android/react-native-inappbrowser/build.gradle Removing package attribute from /path/to/project/android/react-native-inappbrowser/src/main/AndroidManifest.xml 与简化方案对比 如果只需要为缺少 namespace 的库自动设置默认值,可以使用简化版: 1 2 3 4 5 6 7 8 9 10 11 subprojects { afterEvaluate { project -> if (project.hasProperty('android')) { project.android { if (namespace == null || namespace.isEmpty()) { namespace project.group.toString().replace('.', '_') } } } } } 简化方案不会修改任何文件,仅在内存中设置 namespace,但可能无法解决所有第三方库的问题。 参考 GitHub Issue #451 Stack Overflow - namespace not specified Detox Project Setup

2025/10/4
articleCard.readMore

解决 Android Studio 关闭后终端 flutter run 进程自动结束的问题

在 Flutter 开发过程中,很多开发者遇到一个困扰的问题:当使用终端运行 flutter run 命令进行开发时,一旦关闭 Android Studio 或 IntelliJ IDEA,终端中的 flutter run 进程就会自动结束,导致应用停止运行。本文将详细分析这个问题的原因并提供解决方案。 问题现象 典型场景 在终端中执行 flutter run 启动 Flutter 应用 同时打开 Android Studio 进行代码编辑 关闭 Android Studio 或 IntelliJ IDEA 终端中的 flutter run 进程自动结束,应用停止运行 影响范围 通过终端启动的 flutter run 进程 相关的热重载功能失效 调试连接中断 需要重新启动应用才能继续开发 问题原因分析 根本原因 当 Android Studio 启动时,它会自动管理 ADB(Android Debug Bridge)服务器的生命周期。默认情况下,IDE 会: 启动自己的 ADB 服务器实例 接管现有的调试连接 在退出时终止所有相关的调试进程 这种设计导致即使是通过终端独立启动的 flutter run 进程,也会因为 ADB 服务器的关闭而被迫结束。 进程依赖关系 1 终端 flutter run → ADB 连接 → Android Studio 管理的 ADB 服务器 当 Android Studio 关闭时,它管理的 ADB 服务器也会关闭,进而导致所有依赖该 ADB 连接的进程(包括终端的 flutter run)都被终止。 解决方案 配置外部 ADB 服务器管理 最有效的解决方案是让 Android Studio 使用外部手动管理的 ADB 服务器,而不是自己管理一个实例: 配置步骤 打开 Android Studio 设置(Preferences/Settings) 导航到 Build, Execution, Deployment → Debugger 找到 Android Debug Bridge (adb) 部分 在 Adb Server Lifecycle Management 中选择 Use existing manually managed server 设置 Existing ADB server port 为 5037(默认端口) 关键配置说明 Use existing manually managed server: 告诉 Android Studio 不要自己管理 ADB 服务器,而是使用外部已存在的服务器 Existing ADB server port: 指定外部 ADB 服务器的端口(通常为 5037) 这样配置后,Android Studio 不会在启动时接管 ADB 服务器,也不会在关闭时终止它,从而保证终端运行的进程不受影响。 验证配置是否生效 配置完成后,可以通过以下步骤验证: 在终端启动 flutter run 打开 Android Studio 关闭 Android Studio 检查终端中的 flutter run 是否依然运行 如果 flutter run 进程没有被终止,说明配置成功。 总结 通过配置 Android Studio 使用外部手动管理的 ADB 服务器,可以有效解决 IDE 关闭后终端 flutter run 进程自动结束的问题。这种方法的优势在于: 进程独立性:终端和 IDE 的调试进程相互独立 开发效率:无需频繁重启应用 资源优化:避免不必要的进程重启 稳定性:减少因 IDE 操作导致的调试中断 推荐所有 Flutter 开发者采用这种配置方式,特别是那些习惯在终端中运行 flutter run 的开发者。

2025/9/28
articleCard.readMore

使用 grep 查找关键字并显示上下文行

背景 排查日志时,常需要定位关键字并带上一两行上下文确认语义。grep 内建的上下文选项可以直接满足需求,不必再手动 sed -n '19,21p'。 快速示例 假设想在 app.log 中找出包含 Fatal error 的行,并且同时看到上一行与下一行: 1 grep -n -C 1 "Fatal error" app.log -n 会显示行号,便于定位。 -C 1 等价于 --context=1,表示向前向后各多带 1 行。想多看几行时调整数字即可。 输出中,命中的行以冒号分隔行号与内容,上下文行则以短横线 - 连接,快速区分重点。 控制上下文范围 grep 提供三个粒度化参数: -C <N>:两侧各 N 行,是最常用的形式。 -B <N>:只带前 N 行(Before)。 -A <N>:只带后 N 行(After)。 例如只关心关键字后面的调用栈,可使用: 1 grep -n -A 4 "NullPointerException" stacktrace.txt 再配合 -m 1(匹配一次后退出)可以缩短复杂日志的搜索时间。 与常见参数组合 -i:忽略大小写,处理大小写不一致的告警信息很方便。 -E:启用扩展正则,可直接写 grep -E "(WARN|ERROR)"。 --color=auto:高亮命中关键字,在终端阅读更直观。 将这些参数组合成 Shell 函数,后续排查直接调用。例如在 ~/.bashrc 中定义: 1 2 3 4 gctx() { local keyword="$1" file="$2" lines="${3:-1}" grep -n --color=always -C "$lines" "$keyword" "$file" } 执行 gctx "timeout" service.log 2,即可得到行号、关键字高亮、上下文行的结果。 小结 -C/-A/-B 是获取上下文的核心选项,记住数字表示行数即可。 搭配 -n、--color、-m 等参数可以提升排查效率。 如果命中结果过多,将命令与 less -R 或 fzf 管道组合,能够在终端中进行二次筛选,让排查体验更顺滑。

2025/6/24
articleCard.readMore

Android 开发中的三个常见构建错误及解决方案

最近在 Android 项目开发中遇到了几个构建错误,以下是解决方案,供遇到同样问题的开发者参考。 1. META-INF 文件冲突 错误信息 1 2 3 4 5 FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':app:mergeDebugJavaResource'. > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction > 2 files found with path 'META-INF/versions/9/OSGI-INF/MANIFEST.MF' from inputs: 解决方案 在 app/build.gradle 中添加以下配置: 1 2 3 4 5 6 7 android { packagingOptions { resources { excludes += "META-INF/versions/9/OSGI-INF/MANIFEST.MF" } } } 说明 此错误通常由多个依赖包含相同的 META-INF 文件引起,通过 excludes 排除重复文件即可解决。 2. TensorFlow Lite 库冲突 错误信息 1 2 3 Caused by: java.lang.RuntimeException: Duplicate class org.tensorflow.lite.DataType found in modules jetified-litert-api-1.0.1-runtime (com.google.ai.edge.litert:litert-api:1.0.1) and jetified-tensorflow-lite-api-2.12.0-runtime (org.tensorflow:tensorflow-lite-api:2.12.0) 解决方案 在 app/build.gradle 中添加依赖替换规则: 1 2 3 4 5 configurations.all { resolutionStrategy.dependencySubstitution { substitute module("org.tensorflow:tensorflow-lite") with module("com.google.ai.edge.litert:litert:1.0.1") } } 说明 Google 将 TensorFlow Lite 迁移到新包名 com.google.ai.edge.litert,若项目同时包含新旧包名,会导致类冲突。通过依赖替换强制使用新包解决。 3. Jetifier 与 BouncyCastle 兼容性问题 错误信息 1 2 3 4 Caused by: java.lang.RuntimeException: Failed to transform '/Users/xxxxx/.gradle/caches/modules-2/files-2.1/org.bouncycastle/bcprov-jdk18on/1.78/619aafb92dc0b4c6c c4cf86c487ca48ee2d67a8e/bcprov-jdk18on-1.78.jar' using Jetifier. Reason: IllegalArgumentException, message: Unsupported class file major version 65. 解决方案 在项目根目录的 android/gradle.properties 文件中添加: 1 android.jetifier.ignorelist=bcprov-jdk18on-1.78.jar,bcutil-jdk18on-1.78.jar 说明 BouncyCastle 1.78 版本使用 Java 21 编译(class file major version 65),而 Jetifier 不支持此版本字节码。将相关 jar 包加入 Jetifier 忽略列表可避免转换错误。 总结 以上三个问题是 Android 构建中常见的依赖冲突问题,解决思路包括: 排除重复文件 替换冲突依赖 跳过不兼容的处理 遇到类似问题时,仔细分析错误信息,通常能找到相应解决方案。

2025/6/23
articleCard.readMore

使用 flock 解决 Git `unable to read tree` 问题

背景 在 CI/CD 环境下,团队常遇到以下错误: 1 fatal: unable to read tree <SHA> 这通常是多个进程或脚本并发操作同一个 Git 仓库,导致元数据损坏或锁冲突。Git 并非为高并发本地操作设计,因此需要解决并发问题。 问题复现 在自动化脚本中,例如: 1 2 git fetch origin git checkout some-branch 如果多个任务同时执行,可能导致锁冲突或元数据损坏。 解决思路 通过加锁机制,让所有 Git 操作串行执行。flock 是一个简单高效的工具,专为这种场景设计。 flock 安装 Linux 大多数 Linux 发行版自带 flock(属于 util-linux 套件)。如果没有,可按以下方式安装: Debian/Ubuntu: 1 2 sudo apt-get update sudo apt-get install util-linux CentOS/RHEL: 1 sudo yum install util-linux Arch: 1 sudo pacman -S util-linux 安装后即可使用 flock 命令。 macOS macOS 默认不包含 flock,但可通过 Homebrew 安装兼容版本: 1 brew install flock 安装的是 Ben Noordhuis 的 flock,语法与 Linux 版本基本一致。 提示:在 CI 服务(如 GitHub Actions)中,可在步骤中提前安装 flock。 flock 用法 flock 用于在 shell 脚本中对文件加锁: 1 flock <lockfile> <command> 建议将锁文件放在 .git 目录下,避免污染业务代码目录。 实战例子 假设有一个 deploy.sh 脚本: 1 2 3 4 #!/bin/bash git fetch origin git checkout some-branch # ...more commands... 加锁后修改为: 1 2 3 4 5 6 7 8 #!/bin/bash LOCK_FILE="/path/to/your/repo/.git/deploy.lock" flock -n "$LOCK_FILE" bash <<'EOF' git fetch origin git checkout some-branch # ...more commands... EOF 或者直接锁定整个脚本: 1 flock -n /path/to/your/repo/.git/deploy.lock ./deploy.sh -n:表示拿不到锁时立即退出(可选)。 建议将锁文件放在 .git 目录下。 总结 避免并发操作同一个 Git 仓库! 使用 flock 使 Git 操作串行,防止元数据损坏。 Linux 下直接使用,macOS 通过 Homebrew 安装 flock。 锁粒度可适当放宽,确保安全优先。 本地自动化操作 Git 时,flock 是必备工具,简单高效! 如有问题,请在评论区讨论。

2025/6/15
articleCard.readMore

幂等性的劣化:从数学确定性到 AI 不确定性

计算机科学正在经历一场微妙但深刻的转变:幂等性的逐步劣化。从数学的纯粹确定性,到编程中的纯函数,再到面向对象的状态管理,直至今天的 AI 提示工程,我们在用可控性换取表达力,用确定性换取灵活性。 数学的完美世界 纯粹的幂等性 在数学世界里,幂等性达到了最完美形式: 1 2 f(x) = x² f(3) = 9 # 永远是 9,在任何时空、任何计算环境 数学函数的特性: 绝对确定性: 相同输入永远产生相同输出 无副作用: 不依赖外部状态,不改变外部状态 可组合性: 函数可以无限组合而不影响确定性 时空无关: 结果不受时间、地点、环境影响 这种纯粹性源于数学的本质——一个封闭的逻辑系统,完全由公理和推理规则构成。 1 2 3 # 数学式的完美幂等 ∀x ∈ ℝ: sin(π/2) = 1 # 无论计算多少次,在什么时候计算,结果都是 1 编程中的纯函数妥协 理想与现实的第一次碰撞 当数学思想进入编程世界,我们遇到了第一个挑战:物理计算机的限制。 1 2 3 4 5 6 # Python 中的纯函数尝试 def add(a, b): return a + b # 看起来很纯粹,但实际上... print(add(0.1, 0.2)) # 0.30000000000000004,不是精确的 0.3 妥协 1: 浮点数精度 1 2 3 4 5 # 数学: 0.1 + 0.2 = 0.3 # 编程: result = 0.1 + 0.2 print(result == 0.3) # False! print(result) # 0.30000000000000004 妥协 2: 计算资源限制 1 2 3 4 5 6 7 8 # 数学中的递归是无限的 # 编程中必须考虑栈溢出 def fibonacci(n): if n <= 1: return n return fibonacci(n - 1) + fibonacci(n - 2) # fibonacci(100000) → RecursionError: maximum recursion depth exceeded 妥协 3: I/O 的不可避免 1 2 3 4 5 6 7 8 9 10 11 12 # 纯函数无法真正与外界交互 def read_file(path): # 文件内容可能变化 → 非幂等 # 文件可能不存在 → 异常处理 # 磁盘可能损坏 → 外部状态依赖 with open(path, 'r') as f: return f.read() # 同样的调用,可能返回不同内容 content1 = read_file('data.txt') # 返回 "Hello" # 此时文件被修改 content2 = read_file('data.txt') # 返回 "World" 妥协 4: 时间依赖 1 2 3 4 5 6 7 8 9 from datetime import datetime def get_current_hour(): return datetime.now().hour # 同样的函数,不同时刻调用,不同结果 print(get_current_hour()) # 14 # 一小时后 print(get_current_hour()) # 15 函数式编程的防御战 函数式编程社区试图守住最后的阵地: 1 2 3 4 5 6 7 8 -- Haskell 通过类型系统隔离副作用 pureFunction :: Int -> Int pureFunction x = x * 2 impureFunction :: Int -> IO Int impureFunction x = do currentTime <- getCurrentTime return (x + timeToInt currentTime) 但现实是,纯函数的比例在实际应用中不断减少。 OOP 的状态爆炸 幂等性的加速崩溃 面向对象编程的引入,彻底改变了游戏规则。它带来了封装、继承、多态,但也带来了状态的无处不在。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Counter: def __init__(self): self._value = 0 def increment(self): self._value += 1 return self._value # 使用示例 counter = Counter() print(counter.increment()) # 1 print(counter.increment()) # 2 print(counter.increment()) # 3 # 完全非幂等!同样的方法调用,不同的结果 对象内部状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class UserSession { private String token; private LocalDateTime lastActivity; public boolean isValid() { // 结果依赖于内部状态 if (token == null || lastActivity == null) { return false; } LocalDateTime now = LocalDateTime.now(); Duration diff = Duration.between(lastActivity, now); return diff.toMinutes() < 30; // 同样的对象,不同时刻,不同结果 } } 全局状态污染 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 // 全局配置对象(单例模式) public class AppConfig { private static AppConfig instance; private boolean debugMode; private User currentUser; private AppConfig() { this.debugMode = false; this.currentUser = null; } public static AppConfig getInstance() { if (instance == null) { instance = new AppConfig(); } return instance; } public User getCurrentUser() { return currentUser; } public void setCurrentUser(User user) { this.currentUser = user; } } // 任何方法访问这些状态都变成非幂等 public String getGreeting() { AppConfig config = AppConfig.getInstance(); User user = config.getCurrentUser(); return user != null ? "Hello, " + user.getName() : "Hello, Guest"; // 结果依赖全局状态 → 非幂等 } 继承链的状态传递 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class BaseComponent { protected boolean initialized = false; public void init() { this.initialized = true; } } public class ChildComponent extends BaseComponent { private int childState = 0; @Override public void init() { super.init(); this.childState = 42; } public int getValue() { // 结果依赖于 init() 是否被调用 return this.initialized ? this.childState : 0; } } // 使用示例 ChildComponent comp = new ChildComponent(); System.out.println(comp.getValue()); // 0 comp.init(); System.out.println(comp.getValue()); // 42 设计模式: 对抗还是妥协? 面对状态泛滥,我们发明了各种设计模式: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # Singleton: 全局状态的官方认可 class Database: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance.connection = None return cls._instance # 现在整个应用都依赖这个单一状态 # Observer: 状态变化的级联效应 class Subject: def __init__(self): self._observers = [] self._state = None def attach(self, observer): self._observers.append(observer) def notify(self): for observer in self._observers: observer.update(self._state) # 触发连锁反应 这些模式本质上是在承认:我们已经放弃了幂等性,现在只是在管理混乱。 并发的复杂性 随着多线程、异步编程的引入,状态问题进一步恶化: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # Python 多线程的竞态条件 import threading class SharedCounter: def __init__(self): self.value = 0 def increment(self): # 非原子操作! temp = self.value temp += 1 self.value = temp counter = SharedCounter() def worker(): for _ in range(1000): counter.increment() # 两个线程同时操作 t1 = threading.Thread(target=worker) t2 = threading.Thread(target=worker) t1.start() t2.start() t1.join() t2.join() print(counter.value) # 预期 2000,实际可能是 1856、1923... # 完全不可预测! AI 时代的彻底不确定性 从确定性到概率性 如果说 OOP 让我们失去了幂等性,那么 AI 则让我们进入了一个根本上不确定的新世界。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 同样的 prompt,不同的结果 prompt = "请写一个二分查找算法" response1 = openai.chat.completions.create( model="gpt-4", messages=[{"role": "user", "content": prompt}] ) # 可能返回递归实现 response2 = openai.chat.completions.create( model="gpt-4", messages=[{"role": "user", "content": prompt}] ) # 可能返回迭代实现 # 即使设置 temperature=0,也无法保证完全相同 模型层面的不确定性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # 即使是相同的模型、相同的温度 import anthropic client = anthropic.Anthropic() def generate_code(prompt): return client.messages.create( model="claude-sonnet-4.5", max_tokens=1024, temperature=0, # 最低随机性 messages=[{"role": "user", "content": prompt}] ) result1 = generate_code("实现快速排序") result2 = generate_code("实现快速排序") # result1 和 result2 可能在变量命名、注释风格、 # 优化策略上有差异 上下文依赖的复杂性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # 简单问题: 相对确定 prompt1 = "1+1等于几?" # → 几乎总是返回 "2" # 复杂问题: 高度不确定 prompt2 = """ 我有一个 Web 应用,用户反馈加载慢。 技术栈是 React + Node.js + MongoDB。 日活 5000,数据库有 100 万条记录。 请给出优化方案。 """ # AI 需要假设/推断: # - 慢在哪里?前端还是后端? # - 什么查询?是否有索引? # - 服务器配置如何? # - 网络状况如何? # 每次可能给出完全不同的优化建议: # - 增加索引 # - 实现缓存层 # - 代码分割 # - CDN 加速 # - 数据库分片 时间维度的漂移 1 2 3 4 5 6 7 8 9 10 11 12 13 # 2023 年 1 月 response = ask_llm("Python 最流行的 Web 框架是什么?") # → "Django 和 Flask 是最流行的..." # 2024 年 12 月 response = ask_llm("Python 最流行的 Web 框架是什么?") # → "FastAPI 近年来非常流行..." # 同样的问题,因为: # 1. 模型训练数据更新 # 2. 实际技术趋势变化 # 3. 社区共识演变 # "正确答案"本身就在变化 Prompt 微小变化的蝴蝶效应 1 2 3 4 5 6 7 8 9 10 11 12 13 # 版本 1 prompt_v1 = "写一个排序函数" # → 可能返回冒泡排序(简单教学版) # 版本 2(仅添加"高效") prompt_v2 = "写一个高效的排序函数" # → 可能返回快速排序或归并排序 # 版本 3(仅添加"生产环境") prompt_v3 = "写一个生产环境用的排序函数" # → 可能返回 Tim Sort 并附带详细错误处理 # 微小的措辞差异导致完全不同的输出 Prompt 工程: 与不确定性共舞 我们试图通过 Prompt 工程来”驯服” AI 的不确定性: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 prompt = """ 请实现一个 HTTP 服务器的接口限流中间件。 技术要求: - 语言: Python 3.10+ - 框架: Flask - 算法: 令牌桶(Token Bucket) - 限流规则: 每 IP 每分钟 100 请求 代码规范: - 遵循 PEP 8 - 类型注解完整 - 包含 docstring - 添加单元测试 请给出完整实现,包括: 1. 中间件类定义 2. 配置接口 3. 使用示例 4. 单元测试用例 """ # 通过详细约束提高输出质量 # 但不同执行可能强调不同方面 不可逾越的鸿沟 AI 的不确定性是本质性的: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # 概率本质: 每个 token 都是从概率分布中采样 def llm_generate(prompt): # 简化的概念模型 context = encode(prompt) output = [] for _ in range(max_tokens): # 预测下一个 token 的概率分布 probs = model.predict_next_token(context) # { # "def": 0.45, # "class": 0.30, # "import": 0.15, # "from": 0.08, # ... # } # 采样!即使 temperature=0,也是取最高概率 # 但多个 token 概率接近时,微小的数值差异 # 就可能导致不同选择 next_token = sample(probs, temperature) output.append(next_token) context = update(context, next_token) return decode(output) # 每次采样都可能走上不同的路径 即使是”最确定”的情况: 1 2 3 4 5 6 7 8 9 10 11 12 prompt = "请计算: 2 + 2 = ?" # 99.9999% 的情况返回 "4" # 但理论上可能返回: # - "4(十进制)" # - "10(二进制)" # - "在模 2 算术中等于 0" # - "这取决于你使用的数制..." # - 开始解释加法的历史 # 因为 LLM 是模式匹配,不是符号运算 # 完全的幂等性是不可能的 为什么我们接受了这种劣化? 表达力的代价 每一次幂等性的丧失,都换来了更强的表达力: 阶段 获得 失去 数学→编程 可执行性、实用性 无限精度、完美抽象 纯函数→OOP 模块化、可复用、领域建模 函数纯度、可预测性 OOP→AI 自然语言交互、知识泛化、创造力 确定性、可控性、可解释性 Web 开发的实例对比 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 # 纯函数时代: 简单但局限 def calculate_tax(amount): return amount * 0.1 # 完全确定,但功能单一 # OOP 时代: 强大但复杂 class ShoppingCart: def __init__(self): self.items = [] self.user = None self.discounts = [] def add_item(self, item): self.items.append(item) self.recalculate_total() # 级联更新 def apply_discount(self, code): # 依赖外部 API 验证 # 依赖用户状态 # 依赖时间(限时优惠) # 完全非幂等 pass # AI 时代: 智能但不确定 async def optimize_checkout(cart, user_history): prompt = f""" 用户购物车: {cart} 历史记录: {user_history} 请推荐最佳的支付方式和优惠组合 """ # 每次可能给出不同建议 return await ai.complete(prompt) 复杂性的本质 这种劣化反映了我们处理问题的方式转变: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # 数学: 封闭世界假设 # "给定完整的信息,推导确定的结论" theorem = "∀x ∈ ℕ: x + 0 = x" # 编程: 开放世界假设 # "在不完整的信息下,做出最佳决策" def handle_user_input(input_string): # 输入可能是任何东西 # 必须处理边界情况 # 必须应对意外情况 if not input_string: return default_value() try: return process(input_string) except Exception as e: log_error(e) return fallback_value() # AI: 概率世界假设 # "基于模式和统计,生成最可能的答案" def ai_solve(problem_description): # 没有"正确答案" # 只有"合理答案" # 输出是概率分布的采样 return sample_from_distribution( learned_patterns(problem_description) ) 拥抱不确定性 概率性编程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 不是追求"肯定返回 X" # 而是"90% 概率返回 X 类,10% 概率返回 Y 类" class AICodeGenerator: async def generate_function(self, spec): # 生成多个候选 candidates = await asyncio.gather(*[ self._generate_once(spec) for _ in range(5) ]) # 评估质量分布 scores = [self._evaluate(c) for c in candidates] # 选择最优,但承认不确定性 best_idx = scores.index(max(scores)) return { 'code': candidates[best_idx], 'confidence': max(scores), 'alternatives': candidates[:3] } 幂等性的局部保证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # 不是全局幂等,而是关键路径幂等 class PaymentService: def __init__(self): self._processed = {} # 幂等性缓存 async def process_payment(self, transaction_id, amount): # 这个操作必须幂等! if transaction_id in self._processed: return self._processed[transaction_id] # 实际处理(可能非幂等) result = await self._execute_payment(amount) # 缓存结果保证幂等性 self._processed[transaction_id] = result return result async def _execute_payment(self, amount): # 这里可以调用 AI 做风控分析(非幂等) risk_score = await ai_risk_assessment(amount) if risk_score > 0.8: # AI 给出的理由可能每次不同,但决策是幂等的 raise PaymentRejected("High risk detected") return await bank_api.charge(amount) 验证驱动开发 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 # 对于 AI 生成的代码,不追求幂等性 # 而是通过测试保证正确性 class AIAssistedDevelopment: async def implement_feature(self, requirements): # 生成多个实现 implementations = [] for i in range(3): code = await ai.generate_code(requirements) implementations.append(code) # 交叉验证 test_results = [] for impl in implementations: result = await self._run_tests(impl) test_results.append({ 'code': impl, 'passed': result.passed, 'coverage': result.coverage, 'performance': result.performance }) # 选择最可靠的实现 best = max(test_results, key=lambda x: ( x['passed'], x['coverage'], -x['performance'] # 越小越好 )) return best['code'] AI 时代的测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 传统测试: 期望确定的结果 def test_add(): assert add(2, 3) == 5 # 必须精确等于 5 # AI 时代的测试: 期望合理的结果 def test_ai_code_generation(): code = ai.generate("实现 add 函数") # 不测试具体代码,测试行为 assert 'def' in code or 'function' in code assert is_valid_syntax(code) # 动态执行测试 func = compile_and_import(code) assert func(2, 3) == 5 assert func(0, 0) == 0 assert func(-1, 1) == 0 # 关注结果的正确性,不关注实现的一致性 属性测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # Python 中的属性测试(使用 hypothesis) from hypothesis import given, strategies as st # 传统测试 def test_reverse_twice(): assert reverse(reverse([1, 2, 3])) == [1, 2, 3] # 属性测试: 测试性质而非具体值 @given(st.lists(st.integers())) def test_ai_generated_sort(arr): sort_fn = ai.generate_sort_function() sorted_arr = sort_fn(arr) # 测试排序的性质 # 1. 长度不变 assert len(sorted_arr) == len(arr) # 2. 顺序正确 for i in range(len(sorted_arr) - 1): assert sorted_arr[i] <= sorted_arr[i + 1] # 3. 元素相同 assert sorted(sorted_arr) == sorted(arr) 架构层面的应对 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 # 确定性层 + 不确定性层的分离架构 class HybridSystem: def __init__(self): # 确定性组件 self.cache = DeterministicCache() self.validator = DeterministicValidator() # 不确定性组件 self.ai_engine = AIEngine() async def handle_request(self, request): # 1. 尝试确定性路径 cached = self.cache.get(request.key) if cached and self.validator.is_valid(cached): return cached # 幂等返回 # 2. 进入不确定性路径 candidates = await self.ai_engine.generate_responses( request, n=3 ) # 3. 通过确定性验证 valid_candidates = [ c for c in candidates if self.validator.validate(c) ] if not valid_candidates: raise NoValidResponseError() # 4. 选择最优(可能非幂等) best = self._select_best(valid_candidates) # 5. 缓存以提供局部幂等性 self.cache.set(request.key, best) return best 总结 幂等性的劣化,表面上看是能力的退化,实质上是抽象层次的跃升。 四个时代的对比 1 2 3 4 5 6 7 8 9 10 11 # 数学时代: 完美但封闭 f(3) = 9 # 永远如此,但只能计算 # 编程时代: 实用但妥协 square(3) # 通常是 9,但有浮点误差 # OOP 时代: 灵活但混乱 counter.increment() # 每次不同,但能建模复杂系统 # AI 时代: 智能但不确定 ai.solve("优化这段代码") # 每次可能不同,但可能发现我们想不到的方案 核心转变 确定性 → 概率性: 不再问”结果是什么”,而问”结果的分布是什么” 一次正确 → 多次验证: 不再依赖单次执行,而是通过多次采样和验证 完美解 → 满意解: 不再追求理论最优,而是追求实践可行 封闭系统 → 开放系统: 不再假设完整信息,而是适应不完整和变化 实践启示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 1. 分层保证确定性 # 关键路径: 强幂等性(支付、数据一致性) # 辅助功能: 弱幂等性(推荐、搜索) # AI 增强: 非幂等性(内容生成、智能建议) # 2. 用测试替代幂等性 # 不是保证"代码相同" # 而是保证"行为正确" # 3. 拥抱多样性 # 接受 AI 的创造力 # 用验证机制保证质量 # 4. 建立反馈循环 # 收集真实世界的数据 # 持续优化决策边界 幂等性没有消失,它只是变成了一个局部的、概率的、上下文相关的特性。我们不是在失去控制,而是在学习与不确定性共舞。

2024/12/21
articleCard.readMore

Could not create task ':generateDebugRFile' 问题小记

前段时间,处理一个比较旧的 flutter plugin,涉及到 Android 的部分,一顿修改后,发现无法 gradle sync 成功。 报错如下, 1 2 3 4 5 6 7 8 9 10 Could not create task ':generateDebugRFile'. Cannot use @TaskAction annotation on method IncrementalTask.taskAction$gradle_core() because interface org.gradle.api.tasks.incremental.IncrementalTaskInputs is not a valid parameter to an action method. * Try: > Run with --debug option to get more log output. > Run with --scan to get full insights. * Exception is: com.intellij.openapi.externalSystem.model.ExternalSystemException: Could not create task ':generateDebugRFile'. Cannot use @TaskAction annotation on method IncrementalTask.taskAction$gradle_core() because interface org.gradle.api.tasks.incremental.IncrementalTaskInputs is not a valid parameter to an action method. 根据分析上面的错误信息,判定与 gradle 有关,和修改的 kotlin 代码无关。 经过一些简短尝试,最终确定是 gradle 版本不匹配的问题(主要由这一句推断 because interface org.gradle.api.tasks.incremental.IncrementalTaskInputs is not a valid parameter to an action method.)。 原因与解法 原因为 Android Gradle Plugin 与 gradle 不匹配。 可以修改 gradle plugin 版本,也可以修改 gradle 版本。 修改 AGP 版本 classpath ‘com.android.tools.build:gradle:7.1.2’ // The Android Gradle plugin. 修改 gradle 版本 修改gradle/wrapper/gradle-wrapper.properties distributionUrl=https://services.gradle.org/distributions/gradle-8.0-all.zip 修改成(或者对应的gradle 版本) distributionUrl=https://services.gradle.org/distributions/gradle-7.4-all.zip 如何确定 AGP 与 gradle 对应关系 查询,请访问 这里 https://developer.android.com/build/releases/gradle-plugin?#updating-gradle

2024/11/3
articleCard.readMore

Android 模拟器实现 hosts 修改

有时候我们需要使用 Android 模拟器来 绑定一下 hosts 来实现功能的开发与验证,刚好最近遇到了这样的需求,处理完成,简单记录一下。 替换m1 实现(针对 苹果 M1 芯片才需要处理) 下载这个文件 https://github.com/google/android-emulator-m1-preview/releases/download/0.2/emulator-darwin-aarch64-0.2-engine-only.zip 解压,然后将 emulator 和 emulator-check 替换掉这里面的文件 ~/Library/Android/sdk/tools/ (原有的可以备份为 xxx_backup) 查看 avd_id 1 2 3 4 5 6 7 ~/Library/Android/sdk/tools/emulator -list-avds Pixel6ProAPI33 Pixel_3a_API_33_arm64-v8a Pixel_6_API_22 Pixel_6_API_28 Pixel_6_Pro_API_23 Pixel_6_Pro_API_30_X86 启动 avd,可写入状态 1 ~/Library/Android/sdk/tools/emulator -avd Pixel_3a_API_33_arm64-v8a -writable-system 新起终端tab 执行 adb root adb remount adb push your_hosts_on_mac /etc/hosts 验证ping 假设上面的 hosts 我们新增了 127.0.0.1 baidu.com 1 2 3 4 5 6 adb shell ping baidu.com PING baidu.com (127.0.0.1) 56(84) bytes of data. 64 bytes from baidu.com (127.0.0.1): icmp_seq=1 ttl=64 time=1.55 ms 64 bytes from baidu.com (127.0.0.1): icmp_seq=2 ttl=64 time=0.180 ms 注意: hosts 修改建议在 mac 上进行处理,然后使用adb push your_hosts_on_mac /etc/hosts 替换手机内的hosts。手机内置的 vi 很弱,可能无法编辑。 以上。

2023/3/12
articleCard.readMore

Vs Code 快速实现 重写 方法

作为一个从 Android Studio/IntelliJ 切到 VS code 的开发者,一开始会遇到各种不适应的情况。 比如快捷键不一样,使用习惯不一样等。 这里将简单记录一下 个人遇到的一些痛点,比如如何重写方法。 在 Android Studio/ IntelliJ 中,使用起来很简单,比如弹出这个菜单,选择 Override Methods 即可,实现重写 initState 方法 但是切到 Vs Code 后,发现找不到快捷键,后来经过一些摸索,还是找到了 如何快速实现方法重写的方法。 如下图,只需要输入待重写的方法的首字母,即可弹出提示。 VS Code 的方式显得会更加的简单。(后来才发现同样的方式 在 Android Studio/Intellij 也支持,Orz)

2023/2/12
articleCard.readMore

Merge(Pull) Request 推荐的标签列表

一个Merge Request 的 阶段 代码添加或修改,需要进行review 代码review结束,需要修改 重复步骤1和步骤2,直到达到可以合并的标准 角色 MR submitter 负责提交Merge Request,并针对review做修改 MR reviewer 负责review Merge Request,提出MR中存在的问题,该角色可以对应多个人 MR dispatcher 负责分发MR,修改或增加MR reviewer MR terminator 最终负责MR结果走向的人,比如合并或者关闭 注意 上述角色至少需要两个人 因权限问题, MR reviewer 可能无权限合并该MR 有哪些标签 MR:Needs Review(MR:需要Review) 当MR创建或者进行了更新,需要人员Review时,MR submitter 设置该标签 如果MR对应的内容不需要跟版,不需要现在合并的,不要增加该Lable 如果一个MR,当前的label不包含MR:Needs Review,MR reviewer 则不会review MR:Reviewed With Comments(MR:需要修改) MR reviewer 进行了review,并提出了一些评论来记录发现的问题和疑问 MR reviewer 移除 MR:Needs Review 并添加标签 MR:Reviewed With Comments MR submitter 根据提出的问题和疑问进行修改或回答,当修改完毕后,移除标签 MR:Reviewed With Comments,并设置MR:Needs Review Good to Merge(可以合并) 经过上面的来回操作,在某一点,MR达到了一个可以合并的时候,这时候需要移除前面的标签,设置成Good to Merge 设置这个标签,需要由MR reviewer 操作,而不是MR submitter 设置完这个标签后,MR不需要再更新 因MR reviewer不一定有merge权限,这一标签还是有必要的 Do Not Merge(请勿合并) 禁止合并标签,该MR可以被 review,但是不要合并进来. 适用于未来的需求,目前尚不需要加入到主分支 辅助标签,更加明确表明不需要合并当前MR 待验收 该功能没有开始进行验收(测试,UI,UE,产品,数据等) 如果当前MR包含待验收,通常不会被合并 验收中 该功能正在处于验收中(测试,UI,UE,产品,数据等) 如果当前MR包含该标签,通常不会被合并 验收通过 该功能已经通过验收(测试,UI,UE,产品,数据等) 如果当前MR包含验收通过,可以进行合并 BugFix 仅用于修复线上版本的崩溃提交时,使用 可能不上线 有些MR已经完成,但不确定什么版本引入,需要增加该标签 技术需求 非产品迭代需求 References https://engineering.invisionapp.com/post/pr-labels/

2022/11/27
articleCard.readMore

中国特惠!多平台广告屏蔽专家 AdGuard 买断仅需 119 元起

网络垃圾广告令人深恶痛绝,它不仅浪费流量、拖慢设备性能,还存在诸多安全隐患!所以很多用户选用 AdGuard 来对付这种「数字牛皮癣」。 点击[合作伙伴]专属优惠链接,前往数码荔枝下单 AdGuard 终生版最低仅需 119 元,新注册用户再享受立减 5 元优惠! 全方位广告拦截 为了应对无孔不入的垃圾广告,大家也许已经尝试过不少工具。 目前大多数的广告拦截工具仅支持电脑网页端,同时因为技术问题,往往需要逐个浏览器单独配置,最后效果还很一般 (比如面对反屏蔽脚本时无能为力)。 AdGuard 不仅内置防反屏蔽功能,同时支持 Win、Mac、安卓、iOS 四大主流系统,而且一经启用,无须额外配置即可在系统内生效! AdGuard 还可以通过屏蔽垃圾信息,净化网页和软件,让页面更快完成载入,有效节省带宽流量和系统性能。 以视频播放前的贴片广告为例,这类可恶的广告,短则几十秒,长则数分钟!非常浪费时间。 在安装 AdGuard 并启动后,用户再通过浏览器浏览视频,就能享受如同 B 站一般,无广告直接播放的畅快体验 (屏蔽功能不支持国内部分网站和 App)。 以搜索同一个常见疾病为例,在没有拦截广告时,搜索结果通常会置顶多条广告信息。在开启拦截后,搜索结果会清爽得多。 赶快点击[合作伙伴]专属优惠链接购买 AdGuard 畅享无广告的清爽体验,终生版仅需 119 元起! 隐私与保护 无论是大人还是小孩,如今每天接触互联网的时间越来越多。怎样保护个人隐私,怎样让孩子时刻远离危险网站,正困扰着无数人。 除了过滤广告,AdGuard 还提供了「隐形模式」等功能,可以帮助用户远离成千上万的在线跟踪器,保护用户的数据免受网络分析: 它能通过隐藏搜索记录、User-Agent 以及阻止网站通过 Cookie、IP 地址等信息追踪到你,实现对个人信息的有效保护。 AdGuard 还内置了 DNS 保护、危险网站警告、家长控制等功能来为用户保驾护航。当用户即将进入钓鱼、欺诈或恶意网站时,AdGuard 会向用户发出警告。 利用家长控制功能,简单操作就能建立黑白名单,设定允许和禁止访问的网站,配合阻止下载可执行文件的功能,切实避免小朋友接触到有害的网站 / 文件。 实用扩展功能 AdGuard 一大特色在于它提供了强大的可扩展性,通过快速迭代且持续不断的「进化」来切实保护你的网络安全: AdGuard 助手:网页悬浮球帮你管理过滤选项; 弹窗拦截器:拦截烦人的网页弹窗广告; Web of Trust:检测网页的信誉度来判断是否安全; AdGuard Extra:应对常规广告拦截规则无效的复杂情况…… AdGuard 是一款少见的,能同时在广告拦截和网络安全上有着亮眼表现的工具,它在扩展功能上的持续开发更显示出了十足的诚意。 如果你需要一款支持多系统 + 全局广告拦截 + 个人隐私保护 + 儿童安全浏览 + 丰富扩展功能的工具,可以选择的大概只有 AdGuard 了! 它是超级聪明的广告屏蔽专家,更是值得信赖的安全防护墙! 马上点击[合作伙伴]专属优惠链接,AdGuard 终生版最低仅需 119 元!新注册用户再享受立减 5 元优惠,心动不如行动,马上入手吧!

2022/11/27
articleCard.readMore

使用 FVM 解决 flutter 3 无法添加 uploader 问题

Flutter 3 之后,移除了 添加 uploader 的功能,这使得一些使用unpub 的用户很是苦恼,所以想要继续使用命令添加 uploader, 需要切回 flutter 2 才可以。 这里简单介绍一个很便捷的方式来处理上面的问题,就是使用 fvm 来指定 flutter 2 来执行 uploader 添加。 安装 FVM 1 2 brew tap leoafarias/fvm brew install fvm 注: 如果没有安装homebrew, 需要安装一下。 使用 如下内容保存为脚本 addUnpubUploader.sh 1 2 #!/bin/bash fvm spawn 2.10.3 packages pub uploader add $1 --verbose 执行脚本 1 bash addUnpubUploader.sh aaa@gmail.com Referrence https://fvm.app/

2022/11/20
articleCard.readMore

AAPT2 aapt2-7.2.2-7984345-osx Daemon #5: Idle daemon unexpectedly exit. This should not happen 问题解决

CI 构建机,一直有概率出现构建失败的情况,查看了日志,得到了这样的相关错误信息 1 2 3 4 5 6 7 8 9 AAPT2 aapt2-7.2.2-7984345-osx Daemon #7: Idle daemon unexpectedly exit. This should not happen. 15:32:49 [ ] AAPT2 aapt2-7.2.2-7984345-osx Daemon #8: Idle daemon unexpectedly exit. This should not happen. 15:32:49 [ ] AAPT2 aapt2-7.2.2-7984345-osx Daemon #0: shutdown 15:32:49 [ +98 ms] AAPT2 aapt2-7.2.2-7984345-osx Daemon #6: Idle daemon unexpectedly exit. This should not happen. 15:32:49 [ ] AAPT2 aapt2-7.2.2-7984345-osx Daemon #4: Idle daemon unexpectedly exit. This should not happen. 15:32:49 [ ] AAPT2 aapt2-7.2.2-7984345-osx Daemon #3: Idle daemon unexpectedly exit. This should not happen. 15:32:49 [ ] AAPT2 aapt2-7.2.2-7984345-osx Daemon #5: Idle daemon unexpectedly exit. This should not happen. 15:32:49 [ ] AAPT2 aapt2-7.2.2-7984345-osx Daemon #1: Idle daemon unexpectedly exit. This should not happen. 15:32:49 [ +499 ms] The message received from the daemon indicates that the daemon has disappeared. 解决方法 看日志感觉是 Gradle 守护进程的问题 想要既保持 Gralde 守护进程,又要解决这个问题,需要更多的时间 比较简单的方式就是 禁用 Gradle 守护进程。 命令参数传递 适用于能够直接使用gradlew 也适用于不想全局应用配置的情况 1 ./gradlew --no-daemon assembleXXX gradle.properties 适用于 无法直接配置 --no-daemon 的情况,比如 flutter 执行 Android 构建。 适用于全局配置 1 2 ## 修改这个文件 ~/.gradle/gradle.properties 如果没有,直接创建即可 org.gradle.daemon=false 注意: 这里配置完成,最好执行一下./gradlew --stop 确保不适用已有的守护进程。

2022/11/14
articleCard.readMore

git clone 使用代理,实现百倍加速

有时候我们对 github 的仓库进行 clone 的时候,会发现很慢,甚至是龟速,很不够效率。好在有一个简单且快捷的方法来倍速提升clone 效率。 我们通过检索 git 的帮助文档发现有这样的描述 If you just want to use proxy on a specified repository, don’t need on other repositories. The preferable way is the -c, –config <key=value> option when you git clone a repository. e.g. 实践起来 1 git clone https://github.com/flutter/flutter.git --config "http.proxy=192.168.1.6:1611" 上面的例子 通过 --config "http.proxy=192.168.1.6:1611" 设置代理 其中 192.168.1.6:1611 是代理的地址,需要自己搭建或者可用的 上面的配置好,再次执行,基本上可以得到百倍的提效。

2022/10/26
articleCard.readMore

Flutter 处理 Error Setter not found AsciiChar 问题

当我们进行了 flutter 升级后,有时候运行程序会发现无法编译,出现下面这样的错误 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [ ] [ +2 ms] ../../../../.pub-cache/hosted/pub.flutter-io.cn/cached_network_image-3.2.0/lib/src/image_provider/multi_image_stream_completer.dart:152:22: Warning: Operand of null-aware operation '?.' has type 'SchedulerBinding' which excludes null. [ ] [ ] - 'SchedulerBinding' is from 'package:flutter/src/scheduler/binding.dart' ('../../../../code/flutter_3/packages/flutter/lib/src/scheduler/binding.dart'). [ ] [ ] SchedulerBinding.instance?.scheduleFrameCallback(_handleAppFrame); [ ] [ ] ^ [ +402 ms] [ +414 ms] ../../../../.pub-cache/hosted/pub.flutter-io.cn/win32-2.3.1/lib/src/structs.g.dart:554:31: Error: Member not found: 'UnicodeChar'. [ ] [ ] int get UnicodeChar => Char.UnicodeChar; [ ] [ ] ^^^^^^^^^^^ [ ] [ ] ../../../../.pub-cache/hosted/pub.flutter-io.cn/win32-2.3.1/lib/src/structs.g.dart:555:38: Error: Setter not found: 'UnicodeChar'. [ ] [ ] set UnicodeChar(int value) => Char.UnicodeChar = value; [ ] [ ] ^^^^^^^^^^^ [ ] [ ] ../../../../.pub-cache/hosted/pub.flutter-io.cn/win32-2.3.1/lib/src/structs.g.dart:557:29: Error: Member not found: 'AsciiChar'. [ ] [ ] int get AsciiChar => Char.AsciiChar; [ ] [ ] ^^^^^^^^^ [ ] [ ] ../../../../.pub-cache/hosted/pub.flutter-io.cn/win32-2.3.1/lib/src/structs.g.dart:558:36: Error: Setter not found: 'AsciiChar'. [ ] [ ] set AsciiChar(int value) => Char.AsciiChar = value; [ ] [ ] ^^^^^^^^^ 对于这种问题,解决起来也比较简单,执行 1 flutter pub upgrade

2022/10/11
articleCard.readMore