普通视图

发现新文章,点击刷新页面。
昨天以前技术小黑屋

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

作者 androidyue
2024年12月21日 09:30

计算机科学正在经历一场微妙但深刻的转变:幂等性的逐步劣化。从数学的纯粹确定性,到编程中的纯函数,再到面向对象的状态管理,直至今天的 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. 封闭系统 → 开放系统: 不再假设完整信息,而是适应不完整和变化

实践启示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. 分层保证确定性
# 关键路径: 强幂等性(支付、数据一致性)
# 辅助功能: 弱幂等性(推荐、搜索)
# AI 增强: 非幂等性(内容生成、智能建议)

# 2. 用测试替代幂等性
# 不是保证"代码相同"
# 而是保证"行为正确"

# 3. 拥抱多样性
# 接受 AI 的创造力
# 用验证机制保证质量

# 4. 建立反馈循环
# 收集真实世界的数据
# 持续优化决策边界

幂等性没有消失,它只是变成了一个局部的、概率的、上下文相关的特性。我们不是在失去控制,而是在学习与不确定性共舞。



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

作者 androidyue
2025年11月2日 14:30

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



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

作者 androidyue
2025年10月15日 14:30

本文整理 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
使用 HiltRoomRetrofit

使用 @文件 语法

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-apifix/memory-leak-viewmodel)、所有 AI 实验都使用分支、出问题直接删除分支。


8. 建立项目规则和指南文件

规则文件:创建 .editorconfigdetekt.ymldocs/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 视为超级助手而非普通工具,快速试错、频繁提交、大胆回退,让看似不可能的开发效率成为现实。



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

作者 androidyue
2025年10月15日 14:30

通过 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 路径注入风险
  • 考虑添加命令白名单

参考资源



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

作者 androidyue
2025年10月13日 08:00

最近使用 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 处理快速交互。



定位 Android 权限声明来源

作者 androidyue
2025年10月12日 08:00

开发中经常需要排查某个权限是由哪个依赖库引入的,本文记录通过 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 版本的日志格式可能略有差异
  • 查询结果可能包含多个匹配项,需根据依赖关系判断实际来源

延伸阅读



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

作者 androidyue
2025年6月24日 10:30

背景

排查日志时,常需要定位关键字并带上一两行上下文确认语义。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 -Rfzf 管道组合,能够在终端中进行二次筛选,让排查体验更顺滑。


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

作者 androidyue
2025年6月23日 08:33

最近在 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 构建中常见的依赖冲突问题,解决思路包括:

  • 排除重复文件
  • 替换冲突依赖
  • 跳过不兼容的处理

遇到类似问题时,仔细分析错误信息,通常能找到相应解决方案。



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

作者 androidyue
2025年6月15日 08:49

背景

在 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 是必备工具,简单高效!

如有问题,请在评论区讨论。



Android 模拟器实现 hosts 修改

作者 androidyue
2023年3月12日 20:50

有时候我们需要使用 Android 模拟器来 绑定一下 hosts 来实现功能的开发与验证,刚好最近遇到了这样的需求,处理完成,简单记录一下。

替换m1 实现(针对 苹果 M1 芯片才需要处理)

下载这个文件 https://github.com/google/android-emulator-m1-preview/releases/download/0.2/emulator-darwin-aarch64-0.2-engine-only.zip

解压,然后将 emulatoremulator-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 执行

  1. adb root
  2. adb remount
  3. 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 很弱,可能无法编辑。

以上。



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

作者 androidyue
2022年11月27日 21:35

一个Merge Request 的 阶段

  1. 代码添加或修改,需要进行review
  2. 代码review结束,需要修改
  3. 重复步骤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/


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

作者 androidyue
2022年10月26日 08:51

有时候我们对 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 是代理的地址,需要自己搭建或者可用的

上面的配置好,再次执行,基本上可以得到百倍的提效。



iSlide 插件买两年送 180 天,每月不到 6 元钱

作者 androidyue
2022年10月11日 07:13

是不是每次在制作 PPT 时,都丝毫没有头绪?各种模板东拼西凑没有章法?你和 PPT 高手之间,其实就只差一个 iSlide。

iSlide 是一款口碑优秀的 PPT 插件,海量在线资源库,超 30 万专业模板、素材一键插入 PPT,更有一键排版等 38 种辅助功能,真正实现「让 PPT 设计简单起来」。

https://asset.droidyue.com/image/lizhi_io/islide/1.png

​iSlide 目前正在限时优惠中,前往数码荔枝买两年送 180 天,买一年送 90 天,每月不到 6 元钱,新用户首单还能立减 5 元,非常值的价格别错过!

赶快点击[合作伙伴]专属优惠链接收下这款人见人爱的 PPT 神器吧!订阅时长可叠加,趁现在便宜快买买买~

8 大资源库,轻松找素材

iSlide 内置 300,000+ PPT 模板,来自专业 PPT 模板设计团队,有原创正版保障,快速检索一键插入 PPT 文档。

https://asset.droidyue.com/image/lizhi_io/islide/2.png

8 大资源库为你所用,丰富的各类图标 / 图片 / 插图,可在原位置直接编辑替换,做 PPT 告别四处找素材,一个插件整合你要用的所有资源,轻松高效制作精美 PPT!

智能图表实现数据可视化

iSlide 智能图表改变传统图表的单一样式,拥有当下流行视觉化元素和风格,样式新颖更具设计感。

无需耗费大量时间精力,各种参数化调节,iSlide 让图表制作更简单直观,通俗易懂。

https://asset.droidyue.com/image/lizhi_io/islide/3.png

买两年送 180 天,买一年送 90 天,时长可无限叠加,长期需要的用户可多续费几年,每月不到 6 块钱,赶快点击购买吧!

智能对齐排版,专治强迫症

iSlide 为大家提供了丰富的设计工具,涵盖对齐、大小调整、参考线布局、内容选择、矢量、剪贴板、吸附、旋转多种常用操作。告别徒手拖动排版,一键完成各个区域对齐超方便,让幻灯片排版更规范简单。

https://asset.droidyue.com/image/lizhi_io/islide/4.png

无论是创建新文档,还是修改旧文档,都能帮你从繁琐的传统编辑中解脱,提升设计效率,呈现专业。

更细致的色彩优化

iSlide 将全球知名公司的色彩搭配方案共享上传,可以在「色彩库」中浏览并一键应用于当前的 PPT 文档,即便不懂设计,也能呈现专业。

https://asset.droidyue.com/image/lizhi_io/islide/5.png

更多辅助功能

自己做的 PPT,其实是东一页西一页复制来的?iSlide「一键优化」功能非常适合你,只需轻点一下就能统一 PPT 字体 / 段落。

https://asset.droidyue.com/image/lizhi_io/islide/6.png

还有图形矩阵 / 环形布局、图片统一裁剪、补间动画、PPT 瘦身压缩等多项功能,让你的 PPT 设计更简单。

https://asset.droidyue.com/image/lizhi_io/islide/7.gif

现在点击[合作伙伴]专属优惠链接购买 iSlide,买两年送 180 天,买一年送 90 天,每月不到 6 元钱,助你变身幻灯片高手!前往数码荔枝购买的新用户,首单立减 5 元


只有一个 iSlide 插件,还缺少 Office 中的 PowerPoint 软件?前往数码荔枝找拼车,官方牵头安全享受 Office 365 订阅,Excel、PowerPoint、Word 三件套安排上,还有 1TB OneDrive 放心用,点击立即上车



Flutter 3 迁移后编译 warnings 一键修复

作者 androidyue
2022年9月12日 22:15

当我们的 app 支持 flutter 3 后,无论是编译速度,还是运行效率,方方面面会有很大的提升。但是在我们编译的时候,会有类似下面的这些警告。

1
2
3
../../../your_pub/lib/src/framework.dart:275:26: 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').
[        ]     if (SchedulerBinding.instance!.schedulerPhase ==

上面的警告虽然不会影响应用的编译,但是长久来看,还是需要解决的。

原因为何

原因是从 flutter 3 开始, SchedulerBinding.instance返回的是一个 非 null 实例,当我们使用SchedulerBinding.instance!.schedulerPhase 会得到这样的警告Warning: Operand of null-aware operation '!' has type 'SchedulerBinding' which excludes null.

如何解决

解决起来很简单,按照下面的处理,将!去掉即可。

1
SchedulerBinding.instance.schedulerPhase

都有哪些场景

flutter3 开始,下面这些都会有编译警告问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SchedulerBinding.instance!.xxx
SchedulerBinding.instance?.xxx

WidgetsBinding.instance!.xxxx
WidgetsBinding.instance?.xxxx

PaintingBinding.instance?.xxx
PaintingBinding.instance!.xxx


RendererBinding.instance!.xxx
RendererBinding.instance?.xxxx

GestureBinding.instance!.xxx
GestureBinding.instance?.xxx

一键解决

那这么多内容需要解决,有没有一键处理的办法呢?

如果你接触过 终端脚本,答案是肯定的。我们可以使用下面的shell 脚本处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env bash

function sedReplaceFile() {
	echo $1
	sed -i "" -e "s/SchedulerBinding.instance!/SchedulerBinding.instance/g" $1
	sed -i "" -e "s/SchedulerBinding.instance?/SchedulerBinding.instance/g" $1
	sed -i "" -e "s/WidgetsBinding.instance!/WidgetsBinding.instance/g" $1
	sed -i "" -e "s/WidgetsBinding.instance?/WidgetsBinding.instance/g" $1
	sed -i "" -e "s/PaintingBinding.instance?/PaintingBinding.instance/g" $1
	sed -i "" -e "s/PaintingBinding.instance!/PaintingBinding.instance/g" $1
	sed -i "" -e "s/RendererBinding.instance!/RendererBinding.instance/g" $1
	sed -i "" -e "s/RendererBinding.instance?/RendererBinding.instance/g" $1
	sed -i "" -e "s/GestureBinding.instance!/GestureBinding.instance/g" $1
	sed -i "" -e "s/GestureBinding.instance?/GestureBinding.instance/g" $1
	
}

export -f sedReplaceFile
find . -name "*.dart"  | xargs -I {} bash -c 'sedReplaceFile {}'

执行

1
2
cd your_project
f3_fix.sh 

  • 上面的脚本仅在 mac 系统验证, Linux 可能需要自行做简易修改。
  • 如果是 三方pub 包含警告问题,可以选择对应适配 flutter 3 的版本升级即可。


正版软件优惠,近期新品软件推荐

作者 androidyue
2022年9月12日 22:01

不管是手机还是电脑,出色的硬件是好用的基础。而其中的软件工具,也是提高效率、减轻负担的好东西。

免费的软件工具众多,当然付费工具也不少。大家可能会觉得正版软件很贵,但国内软件代理商的价格其实很实惠。

本次为大家介绍,优秀代理商「数码荔枝」近期上架的多款软件工具。分别是多平台抠图工具:傲软抠图,以及无需剪辑的在线视频制作工具:右糖

点击我们的专属链接,直接购买即为优惠价格。


傲软抠图 [Win/Mac/iOS/Android]

傲软抠图是一款适用于多平台的抠图工具,支持 AI 智能一键抠图,内置算法针对人物、物品等实现自动抠图,效果精细、清晰干净。

https://asset.droidyue.com/image/lizhi_io/collect_sep/%E5%9B%BE1.png

傲软抠图使用非常简单,只需打开软件,选择照片,就能自动完成抠图,速度快效果好。提供手动优化,可任意保留或擦除抠图区域,支持裁剪、移动等操作。

https://asset.droidyue.com/image/lizhi_io/collect_sep/%E5%9B%BE2.gif

支持人像物品背景去除 / 更换,内含海量素材,可快速更换背景画面,打造精美照片,无论是用于人像后期还是产品图制作都合适。

https://asset.droidyue.com/image/lizhi_io/collect_sep/%E5%9B%BE3.png

可制作各类证件照,支持一键修改背景颜色、照片尺寸、文件大小,证件照换底色再也不用去照相馆啦~

https://asset.droidyue.com/image/lizhi_io/collect_sep/%E5%9B%BE4.png

支持批量抠图 / 图片清晰处理,并提供制作图章、图片去水印功能,帮助用户高效处理照片。

https://asset.droidyue.com/image/lizhi_io/collect_sep/%E5%9B%BE5.png

一个账号即可通用 Win / Mac / 安卓 / iOS 平台,方便随时抠图。

https://asset.droidyue.com/image/lizhi_io/collect_sep/%E5%9B%BE6.png

傲软抠图提供免费试用,试用效果满意,可点击专属链接购买抠图使用张数,仅需 20.3 元起。兑换后不会过期,无时间限制。

右糖 [Web]

右糖是一个基于云端的在线视频制作工具,手机电脑皆可使用。无需视频剪辑技巧,直接选用模板上传素材,一键就可以制作充满设计感的动画视频、电子回忆相册。

https://asset.droidyue.com/image/lizhi_io/collect_sep/%E5%9B%BE7.png

网站含婚礼、教育、商业宣传等 800+ 优质模板,各类风格场景持续更新。选择模板 > 上传素材 > 一键制作,只需三步,五分钟就能生成电子相册。

https://asset.droidyue.com/image/lizhi_io/collect_sep/%E5%9B%BE8.png

内置丰富的画面库可供选择编辑,类型多样,制作动画视频一键应用成片超方便。

https://asset.droidyue.com/image/lizhi_io/collect_sep/%E5%9B%BE9.png

制作视频不知如何搭配 BGM?这里有海量流行音乐曲库,让你的视频更「声」动。

https://asset.droidyue.com/image/lizhi_io/collect_sep/%E5%9B%BE10.png

也无需为文案发愁,文案库为你准备了各类精彩好句,无惧灵感枯竭。可为画面添加字幕、Logo,进行裁剪旋转、排序、切换比例等操作。

https://asset.droidyue.com/image/lizhi_io/collect_sep/%E5%9B%BE11.png

素材作品在云端渲染并存储,不怕硬件性能不给力,手机电脑皆可操作,随时随地观看与分享。

https://asset.droidyue.com/image/lizhi_io/collect_sep/%E5%9B%BE12.png

支持免费预览动画或相册效果,满意后会员可解锁 720P / 1080P 画质下载。

右糖会员版提供三个可购版本,一月版、季度版及一年版可选。其中一年版可制作 300 个视频,每个视频不到 1.5 元点击链接获取,大家可按需选购。

数码荔枝代理了众多优秀工具,它们也会不定期上架新的软件产品,大家可以持续关注。点击了解更多正版软件



关于仓库的批量处理脚本,效率提升 500%

作者 androidyue
2022年9月4日 21:57

很多时候,我们会遇到这样的场景

  • 换了新电脑,需要挨个 clone gitlab repos?
  • 无法确定哪个 repo 包含了 maven.aliyun.com 这个设置?
  • 能否批量更新 本地的 repos?

如果你有上述的疑问或者情况,你可以尝试本文中的一些批量处理 repos 的方法

批量 clone

1
ruby cloneRepos.rb code-git-xxxxxx ../projects/

其中 cloneRepos.rb 脚本内容如下

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
#!/usr/bin/env ruby
# encoding: utf-8
require 'httparty'
require 'json'


def cloneRepos(repoUrlApi)
    headers = {
        'PRIVATE-TOKEN': ARGV[0],
    }

    response = HTTParty.get(repoUrlApi, headers: headers)
    data = JSON.parse(response.body);
    puts data.length()
    data.each { |e|
        name = e['name']
        gitUrl = e['ssh_url_to_repo']
        system "cd #{ARGV[1]} && git clone  #{gitUrl}"
        puts name
    }
end


cloneRepos('https://code.hahaha.io/api/v4/projects?per_page=100')
cloneRepos('https://code.hahaha.io/api/v4/projects?per_page=100&page=2')

参数解释

  • code-git-xxxxxx 从 gitlab 获取的token,根据下图指示获取

https://asset.droidyue.com/image/2022/h2/QQ20220904-220957%402x.png

  • ../projects 存放的目录

注意

这个脚本目前只能处理 前200个 repos,如果有需要,可以自行修改代码处理。 批量工程检索

快速查找

比如我们想要搜索 maven.aliyun.com

1
2
3
4
5
6
7
8
9
projects gradleSearch.sh maven.aliyun.com

./xxxx/example/android/build.gradle:7:            url 'http://maven.aliyun.com/nexus/content/repositories/releases/'

./xxxxx/example/android/build.gradle:23:            url 'http://maven.aliyun.com/nexus/content/repositories/releases/'

./xxxx/android/build.gradle:22:            url 'https://maven.aliyun.com/repository/public/'

./xxxx/example/android/build.gradle:6:        maven {url 'https://maven.aliyun.com/repository/google'

其中 gradleSearch.sh 的内容如下

1
2
#!/bin/bash
find . -name "*.gradle" | xargs grep -E -n --color=always -r "$1"

批量更新

1
updateRepo.sh

它的内容是这样的

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
for dir in */; do
    echo "$dir"
    realpath=`realpath $dir`
    echo $realpath
    cd $realpath
    git checkout master
    git pull origin master
    cd -
done

注意

  • 如果当前repo 有未提交修改,则无法更新。

通过上面的几个脚本,我们可以轻松实现效率提升。



JetBrains 系列即将涨价,抓紧最后的好价入手!

作者 androidyue
2022年9月4日 21:40

对于开发者来讲,每天都要和各种编程 IDE 和开发工具打交道。想必一定听说过 JetBrains 公司,或旗下的产品:IntelliJ IDEAPyCharmWebStorm 等。

不久前,JetBrains 发布公告,他们即将上调系列产品的订阅价格!

https://asset.droidyue.com/image/lizhi_io/jetbrains/%E5%9B%BE1.png

涨价风暴将席卷旗下一众产品,包括:IDE、.NET 工具和 All Products Pack。

此次价格调整将于 2022 年 10 月 1 日 生效,届时订阅价格将上涨 10%~20%,以下是部分个人版的涨价情况:

ReSharper:¥869 > ¥939 WebStorm:¥399 > ¥469 All Products Pack:¥1679 > ¥1949 GoLand、CLion、RubyMine……:¥599 > ¥669

https://asset.droidyue.com/image/lizhi_io/jetbrains/%E5%9B%BE2.png

JetBrains 这些产品对于开发者来讲可是刚需呀!知名代理商「数码荔枝」目前价格还未调整,大伙记得趁涨价前赶紧囤一波订阅,别等到涨价了才后悔错过优惠,现在就是入手的最佳时机!

点击专属链接优惠订阅 / 续费 JetBrains 系列软件

囤货的朋友还得注意一下,个人订阅允许最多至三年,企业订阅最多至两年!

优秀、专业且强大

工欲善其事,必先利其器。对于开发者来说,出色的编程技能固然重要,但要让敲代码变成轻松愉悦的享受,自然得备上一款能提升开发效率的好工具。

自带代码辅助功能的 JetBrains 系列软件,非常值得各位开发者拥有。

包含 IntelliJ IDEA、PyCharm、WebStorm、AppCode、CLion、DataGrip、GoLand、PhpStorm、RubyMine 等。

https://asset.droidyue.com/image/lizhi_io/jetbrains/%E5%9B%BE3.png

各位使用 Java、PHP、Python 还是 Swift 开发语言的朋友,都能找到合适的开发工具。

JetBrains 具备优秀的智能编码辅助和代码重构能力,强大、稳定的调试器帮助开发者节省更多调试时间。

https://asset.droidyue.com/image/lizhi_io/jetbrains/%E5%9B%BE4.png

提供了包括提交、冲突解决、分支切换和分支对比在内的源码管理功能,帮助实现源代码高效版本控制。

https://asset.droidyue.com/image/lizhi_io/jetbrains/%E5%9B%BE5.png

集成 Docker、版本控制、反编译器等众多开发工具,丰富的插件生态系统,让开发者可以根据自己的特定需求定制 IDE。

https://asset.droidyue.com/image/lizhi_io/jetbrains/%E5%9B%BE6.png

优秀、专业且强大的工具,开发者和团队必备。现在续费还能获得额外折扣,不要错过!点击专属链接优惠入手 JetBrains 系列软件

https://asset.droidyue.com/image/lizhi_io/jetbrains/%E5%9B%BE7.png

All Products Pack 更划算

而对于全栈开发者来说,JetBrains 的全套编码工具更是开发时的不二之选。集合 IntelliJ IDEA、PyCharm、GoLand 等十余个 IDE,多个 .NET 开发工具以及分析器。

https://asset.droidyue.com/image/lizhi_io/jetbrains/%E5%9B%BE8.png

实力与优惠兼得的 JetBrains 编程开发工具集,涨价后 1949 元,现在还能以 1679 元的限时特价入手,立省几百!点击专属链接优惠订阅 All Products Pack

产品汉化出色

JetBrains 系列产品现已完成汉化,无需另外下载软件安装,大部分软件内直接安装语言插件,就可以显示简体中文界面。

中文操作界面,新手更好入门!

https://asset.droidyue.com/image/lizhi_io/jetbrains/%E5%9B%BE9.png

铂金代理 品质售后无忧

购买专业软件不仅要选对代理商,还得看靠谱的售后服务。

数码荔枝拥有开发商 JetBrains 的铂金代理资格。所销售的正版软件品质可靠,值得开发者信赖!

https://asset.droidyue.com/image/lizhi_io/jetbrains/%E5%9B%BE10.png

除了可靠的授权,它们店铺的客服同样专业,可以为用户解决众多软件上的使用问题。在这购买 JetBrains 系列软件,使用自然没有后顾之忧。

https://asset.droidyue.com/image/lizhi_io/jetbrains/%E5%9B%BE11.png

对于软件激活与授权安装的问题,它们的客服会提供专业售后服务。更多软件使用问题还可在官方平台提交工单,获得开发商的专业支持。

JetBrains 系列产品的优秀有目共睹,除了官网,开发者现在又多了「数码荔枝」这样专业靠谱的购买渠道。

快趁涨价前带走这些工具,高效助力你的开发!点击链接马上订阅 / 续费 JetBrains 系列软件



unpub 发布原子化处理

作者 androidyue
2022年8月16日 07:43

目前 unpub 作为我们重要的 pub 私有服务托管着 众多的 pubs。在日常的开发过程中,我们也会对pub 做出了一些约束。比如

  • 只允许在 master 或者 release/* 分支发布
  • 不符合上述条件的分支不允许发布。

今天我们讨论的问题重点,非上述的问题,而是发布 unpub 的原子性。

非原子化的两步

在讨论原子性之前,我们需要明确有两个步骤。

  • 我们需要执行 flutter packages pub publish --server=https://pub.aaa.com/ --verbose 进行发布
  • 在发布unpub 前后,我们需要将代码推送到远端 gitlab 服务器

非原子化的问题

那么如果我们忘记了,最后一步的推送工作,带来的问题可能会很严重

  • 某份代码 A 未被推送,后面的更改再发布 unpub 导致 代码 A 的功能 丢失
  • 后续发现丢失后,找回代码A 很可能无法 确定 当时的代码修改内容(可能包含其他修改,人的记忆里不总是可靠)
  • 找回后,往往需要投入一定的测试资源验证较为稳妥。

所以,将上面两部合成一步,来作为一个原子操作,显得尤为重要。

一个脚本原子化

这里有一个简单,却极为实用的方式,就是这个代码

1
2
3
#!/bin/bash
git push origin "$(git symbolic-ref -q HEAD 2>/dev/null | cut -d'/' -f 3)"
flutter packages pub publish --server=https://pub.aaa.com/ --verbose

下载到本地后,设置文件可执行(加入环境变量),然后当再次发布 unpub 时 这样处理即可。

1
unpubUpload.sh


❌
❌