Published
- 12 min read
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. 默认决策规则
当存在多种可实现方案时,默认选择最容易让新读者回答以下问题的方案:
- 这段代码在做什么?
- 这个输入为什么走这条路径?
- 这条规则定义在哪里?
- 如果我要修改它,应该改哪里?
- 改动后我该如何验证?
---