Home

Published

- 12 min read

Benchmark 经验

img of Benchmark 经验

速记

  • 做 benchmark 之前首先要让 GPT 去找到合适的相关 benchmark,可以减少大量制作时间。
  • 制作 benchmark 时应该人工设置或者与 GPT 讨论具体设置哪些类别和具体字段,这是 benchmark 结构上的规范,不应该完全交给 GPT
  • 让 GPT 帮忙从相关 benchmark 中获取原始的样本,得到第一版的 benchmark
  • 设置评估方法:应该让 GPT 先综合已有的 benchmark 的评估方法,然后提取出共同的部分,并进行人工确认,得到最后应该使用的评估策略和方法
  • 让 GPT 完成评估代码,要求遵守扁平、低抽象等开发时编码策略(使用专门 .md 文件作为编码风格指导),方便我们阅读和理解
  • 测试模型的性能,并让 GPT 写 HTML 前端进行可视化,需要展示所有能展示的细节
  • 人工检查评估结果中得分最好的样本,检查是否存在误判。如果存在误判,则将他们的评估细节全部提取出来,交给 GPT 去分析如何增强评估代码以减少误判
  • 反复多次,直到误判很少或者消失(一般误判很少即可认为有统计意义)

重点

  • 不能完全交给 GPT,需要人工把控关键节点
  • 评估结果展示十分重要,应该让 GPT 按照好的 TASTE 去写前端代码展示评估结果
  • 评估代码的编写要阅读友好,因此使用好的 TASTE 去约束代码风格十分重要

编码风格

# 编码风格

## 核心原则
代码优先追求:

- 清晰
- 可读
- 易修改
- 行为可预测

宁可接受适度重复,也不要为了减少几行重复而引入过度抽象、隐式耦合、复杂配置或难以追踪的执行路径。

写出的代码应让人能够快速看懂:

- 输入是什么
- 经过了哪些处理
- 为什么走这条分支
- 最终输出是什么

## 2. 通用设计偏好

- 优先写直白、显式、容易理解的代码,而不是“设计感很强”但需要多次跳转才能读懂的代码。
- 优先让真实业务规则直接出现在代码表面。
- 除非复杂度确实需要,否则不要过早引入以下结构:
  - registry
  - 插件系统
  - 策略模式
  - 抽象基类层级
  - 多层配置驱动
  - DSL
  - 元编程式分发
- 抽象必须服务于可读性和可维护性,而不是服务于形式上的优雅。

---

## 3. 结构原则

- 模块职责要单一且直接可见。
- 每个模块应该有一个清楚、明显的目的。
- 例如:
  - 路由模块负责路由
  - 校验模块负责校验
  - 解析模块负责解析
  - 存储模块负责存储
  - 业务模块负责业务处理
- 重要逻辑不要分散到过多文件和过多抽象层中。
- 如果理解一个核心行为需要跨很多层追踪,通常说明结构过于间接。

---

## 4. 数据流偏好

尽量保持数据流简单、直接、可追踪:

- 输入
- 明确的校验 / 路由 / 转换
- 明确的中间结果
- 明确的输出

不要让关键处理依赖以下方式才能理解:

- 隐式默认值
- 隐含字段语义
- 动态映射表
- 多层 helper 拼接
- 分散在多个配置文件中的规则

重要数据约定应在代码中显式表达。

---

## 5. 控制流偏好

- 优先使用扁平的 `if/elif`、少量明确函数和直接返回。
- 尽量避免深层嵌套。
- 如果嵌套过深,优先考虑:
  - 提前返回
  - 拆分成少量辅助函数
  - 把异常情况提前处理掉
- 只要逻辑仍然清楚,优先显式分支,而不是抽象成分发表、注册器或复杂调度机制。
- 重要业务分支应该直接写出来,让人能看出“为什么会走这里”。

**示例**

```python
if operation_type == "create":
    return handle_create(request)
if operation_type == "update":
    return handle_update(request)
if operation_type == "delete":
    return handle_delete(request)
raise ValueError(f"Unknown operation_type: {operation_type}")
```

---

## 6. 抽象规则

不要为了“消除几行重复”而抽象。

只有在以下情况下才值得抽象:

- 重复已经明显影响维护
- 抽象后的名字比原始实现更容易理解
- 抽象不会掩盖关键业务差异
- 抽象能降低修改成本,而不是提高理解门槛

如果抽象会让代码读者看不出真实规则,那就不要抽象。

对于固定任务或固定类型处理,优先写多个显式函数,而不是写一个高度参数化、内部充满模式分支的大函数。

---

## 7. 函数设计

- 函数职责要明确。
- 函数名要直接表达业务意图。
- 参数应尽量少而明确。
- 避免传入一个“万能 context / config / options”对象,然后在函数内部做大量隐式判断。
- 如果一类处理本身就是固定流程,优先写显式函数,例如:
  - `build_user_payload()`
  - `validate_order_request()`
  - `parse_api_response()`

如果一个函数需要很多布尔参数、模式参数或配置参数才能工作,通常说明这个函数承担了太多职责。

---

## 8. 命名风格

- 命名要贴近业务语义。
- 变量名、函数名、字段名应让读者尽量不靠猜测就能理解其用途。
- 避免过于泛化或空泛的名字,例如:
  - `process`
  - `handle`
  - `manager`
  - `data`
  - `utils`
  - `common`
- 更推荐使用能体现真实语义的名字,例如:
  - `operation_type`
  - `order_status`
  - `user_profile`
  - `template_id`
  - `validate_payment_request`

如果为了兼容旧接口需要保留旧字段,可以暂时保留,但应增加语义更清晰的新字段,并让新字段逐步成为内部主语义。

---

## 9. 数据与接口处理

- 输入输出格式要明确。
- 字段名、schema、顺序要求、默认值、兼容行为等,应在代码中显式定义。
- 不要依赖隐式约定来维持正确性。
- 内部标准格式可以和外部接口格式分开。
- 外部接口保持兼容时,内部仍应尽量统一、规范、可预测。

例如,外部字段保留旧格式,但内部先做标准化:

```python
normalized_status = raw_status.strip().lower()

result = {
    "status": raw_status,                  # external compatibility
    "normalized_status": normalized_status # internal standard
}
```

---

## 10. 错误处理

- 错误处理要明确,不要静默吞掉异常。
- 能在边界校验的问题,尽量在边界校验。
- 报错信息应帮助定位问题,尽量说明:
  - 哪个输入有问题
  - 期望是什么
  - 实际收到什么
- 不要为了流程“顺滑”而模糊错误来源。

例如:

```python
raise ValueError(f"Unknown template_id: {template_id}")
```

优于:

```python
raise Exception("invalid config")
```

---

## 11. 先满足当前需求,再考虑未来扩展

- 优先针对当前明确需求设计代码。
- 不为假设中的未来需求提前引入复杂架构。
- 不为了“未来可能扩展”而过早构建通用框架。
- 如果未来确实出现更多变体、更多模式或更多规模,再在当时做重构。

默认倾向是:先写清楚、可用、容易改的实现,而不是先做“理论上更可扩展”的设计。

---

## 12. 改动应局部且可验证

- 新需求优先通过局部、直接、容易验证的修改来实现。
- 尽量让一个改动只影响少量明确位置。
- 避免一个小需求需要同时修改多个抽象层、共享框架或隐式约定。
- 代码结构应支持快速定位改动点和验证影响范围。

理想状态下,维护者应能快速回答:

- 要改哪一段逻辑
- 为什么在那里改
- 改完会影响哪些路径
- 如何验证改动是否正确

---

## 13. 避免事项

避免:

- 过度抽象
- 过度参数化
- 深层嵌套条件
- 为了少量重复引入复杂共享层
- 把关键规则藏在映射表、配置、注册器或动态分派里
- 把真实执行路径拆散到多个文件和多层间接调用中
- 看起来“优雅”,但让读者更难确认实际行为的设计

不要为了风格上的高级感牺牲可读性、可修改性和可验证性。

---

## 14. 默认决策规则

当存在多种可实现方案时,默认选择最容易让新读者回答以下问题的方案:

- 这段代码在做什么?
- 这个输入为什么走这条路径?
- 这条规则定义在哪里?
- 如果我要修改它,应该改哪里?
- 改动后我该如何验证?

---