核心结论

科研代码“运行成功”只能证明这次没有报语法错误,不能证明它选对了样本、保留了配对关系,或算出了方案要求的指标。AI 写出的代码应该像实验测量一样接受校准:先给它明确规则和已知答案,再允许它处理正式数据。

本文用一段神经科学试次汇总代码说明具体做法。贯穿始终的产物是一份代码验收合同,里面同时写科学规则、失败条件、测试答案和交付环境。代码换了实现方式没有关系,只要它仍能通过这份合同。

旧脚本的数字对得上,处理顺序却错了

实验让每名参与者完成两个条件下的反应时任务,每个条件包含多个试次。旧脚本由已经离组的学生编写,只留下一个数据文件和最终均值表。团队想请 AI 重写成 Python,以便用于新增样本。

原始表一行是一个试次,含参与者编号、条件、试次编号、反应时和正确性。实验方案要求先保留正确试次,再按每名参与者、每个条件汇总有效试次数、均值和中位数;预设范围外的反应时只加标记,不在这个步骤自动删除参与者。

AI 第一版脚本顺利生成结果,旧样本的总体均值甚至十分接近旧表。小型测试却显示,它先计算了全部试次的平均值,再删掉错误反应。两个错误试次恰好接近总体均值,所以汇总表只差一点;换一批数据,偏差可能很大。

测试参与者 S01,条件 A
正确试次反应时:400、500、600
错误试次反应时:2000

合同中的期望:n=3,均值=500,中位数=500
AI 第一版输出:n=3,均值=875,中位数=550
原因:均值在过滤错误试次之前已经计算
决定:第一版失败,修改处理顺序并重跑全部测试

若团队只比较最后一位小数,或者只问 AI “这段代码有没有 bug”,这项错误很可能留下。已知答案把研究规则变成了可执行证据。

验收合同把研究决定写成程序边界

合同没有写“整理并优化数据”,而是逐项约束这次任务:

合同位置明确内容验收方式
输入一行一个试次;字段类型、单位、允许条件与复合主键缺列、重复键或未知条件时立即停止
研究单位参与者是独立单位,试次是其内部观察输出不能把试次数当参与者数
处理顺序验证结构→标记范围→保留正确试次→按人和条件汇总小型样本逐步核对行数
输出有效试次数、均值、中位数、标记数和排除原因与人工表逐项比较
禁止行为覆盖原始文件、把缺失填零、静默改标签、遇错后自动换规则错误测试必须失败并给出位置
运行证据数据与代码版本、依赖、命令、日志和输出校验新环境从说明开始重跑

“标记范围”和“决定排除”有意分开。某次反应很慢,可能是设备故障,也可能是真实表现;代码只能按方案生成标记。是否排除、依据来自哪里、会影响哪些分析,由研究者在数据决定中另行确认。AI 若直接调用常见异常值函数删除记录,就违反合同,即使最终图更平滑。

单位也写进合同。反应时输入为毫秒,输出不得无说明地换成秒。参与者编号保持字符串,不能因数值化丢掉前导零。条件标签只能是 A 或 B,遇到小写 a 时程序报告未允许值,不自行猜测它与 A 相同。

五个小测试比正式数据更早暴露问题

团队没有用同一类正常样本反复测试,而是给不同错误设置明确期待。

T01 是上面的正常计算;T02 让一个条件全部缺失,预期保留该参与者与缺失状态,而非生成零均值;T03 放入重复试次编号,脚本必须停止并列出复合键;T04 把反应时放在阈值边界上,确认“等于阈值”究竟归入哪一侧;T05 检查所有输出有效试次数之和,必须等于过滤后正确试次数。

第二版通过 T01,却在 T02 中直接丢掉整名参与者。问题来自分组函数的默认行为,不是 AI 在文字解释中承认的“保留缺失”。团队修改输出结构并新增回归测试:以后调整任何模块,T01—T05 都重新运行;若预期确实因方案变更而改变,先更新合同和批准记录,再改测试。

团队还准备一项破坏性测试,把反应时列写成字符 five hundred。程序应该指明文件、行号和字段,不能强制转换成缺失后继续。科研流程里,明显失败通常比悄悄得到不完整数字更安全。

模块之间也保存一张数量对账表。加载模块报告文件数和行数,验证模块报告重复键与非法值,过滤模块分别报告正确、错误和未知状态,汇总模块再报告每位参与者、每个条件的贡献。若上一模块留下 13,104 个正确试次,下一模块只汇总到 13,090 个,流水线就失败,而不是把 14 个试次当成“正常损耗”。

测试还覆盖不变量:汇总表中的有效试次数加总等于输入中通过规则的试次数;参与者不会在没有排除记录时消失;输出条件集合不会凭空增加;重复运行相同输入得到相同校验值。它们不验证某个统计方法是否科学合适,却能拦住实现偏离既定规则。

关键结果另用电子表格手算,并与旧脚本在一份已经人工确认的子集上比较。旧脚本不是天然真值,差异需要回到规则解释;也不能让同一个 AI 再生成一份相似代码作为“独立复算”,因为两个实现可能共享同一误解。

正式运行要留下每一次样本变化

通过测试后,脚本读取冻结的正式输入。日志显示加载 14,280 个试次,结构验证没有重复键;正确性规则留下 13,104 个;范围标记影响 37 个,但本步骤未据此删除;最后生成参与者—条件汇总。这里的数字是教学运行记录,用来说明每次变化都应能对账。

一位参与者只完成条件 A。旧脚本在最终宽表转换时把此人整行删除,AI 第二版则按合同保留 A 的结果和 B 的缺失。团队在下游统计说明中再决定该参与者能否进入特定比较。代码不能因为最终模型偏好完整案例,就提前替分析做纳排决定。

汇总结果登记输入数据版本、代码提交、运行时间和校验值。图表和统计脚本只读取这份已放行输出;研究者不能打开表格手工改一个均值再另存为“最终版”。若源记录更正,流程从输入开始重跑并产生新版本。

审查也不止看代码风格。领域研究者确认正确试次和反应时规则,分析者确认汇总单位,软件审查者检查实现、测试和错误行为。每位审查者只确认自己真正核对过的部分,不能由 AI 自动填写“全部通过”。

审查时,一段简洁的链式处理被拆开,因为它在一行里同时改类型、过滤和汇总。拆开并非追求更多函数,而是让日志能指出哪一步改变了多少记录,也让领域研究者在不理解所有语法的情况下核对处理顺序。代码更短不是这项任务的优先目标,科学决定可见才是。

在一台干净环境里,才发现遗漏的手工步骤

开发电脑上的脚本依赖一个研究者早先手工建立的 conditions.csv,代码却没有在说明中提到。因为文件一直存在,日常运行从未报错。团队在新的虚拟环境中按 README 执行时,流程在第一步失败,这才发现缺少映射表及其来源。

修复不是把文件复制进去就算完成。团队将获准共享的映射作为版本化输入,记录生成方法,并增加校验:映射键唯一、标签属于允许集合、数据中的所有条件均能匹配。随后从空环境安装锁定依赖,运行测试,执行正式入口,比较关键统计量和输出校验。

空环境重跑还暴露出绘图库升级后默认排序变化。数值没有变化,条件 B 却跑到 A 前面,导致后续图的颜色含义反转。团队没有只固定图片顺序,而是在数据输出中显式保存允许的条件顺序,并增加测试。依赖版本仍被锁定;显式规则又避免未来升级时静默依赖旧默认值。

美国国家科学院关于可复现性与可复制性的报告把相同数据、计算步骤、方法、代码和分析条件联系起来;Software Sustainability Institute 的实践文章也强调输入输出、依赖与运行方法。对本案例,真正证明可重建的是另一名成员按记录得到相同关键结果,而不是作者说“代码可复现”。

真实数据不能离开安全环境时,可以用结构一致的合成数据检查安装、接口和错误路径,再由获批人员在安全环境运行正式数据。合成数据证明软件流程能够工作,不证明真实分析已经正确,二者的运行记录应分开保存。

改一个规则时,不能只替换一张图

项目后期,团队决定把一个反应时边界的比较从“小于”改成“小于等于”。这项修改先更新方案决定和合同,再改变实现。回归测试指出 6 个试次的标记状态变化;依赖清单同时列出汇总表、主模型、图 2 和结果段两处数字。

所有受影响产物从代码重新生成。若主结果未变化,也保存这项核对;若结果变化,作者重新审查解释。手工把图里的一个点移回去,会切断数据、代码与论文的关系,因此不属于可接受修改。

这次变更后,37 个标记中有 6 个边界状态变化,但纳入规则并不按标记自动删除,所以正式汇总值没有改变。团队仍生成新的日志和版本,明确“检查过且无下游数值变化”。这比不留下记录更有用:后来审稿人追问阈值时,作者能够说明修改发生过,也能证明结论为何未变。

交付包最终包括固定入口命令、依赖锁定、字段说明、合成测试、运行日志、输出清单和已知限制。它明确只支持当前两条件任务,不接受未知编码,也不负责判断异常试次的科学去留。以后换实验、换数据结构或换研究问题,需要重新写合同和验证,不能沿用“本项目已经通过”的结论。

AI 在这里确实节省了重写、补测试和整理日志的时间。但代码质量的证据来自失败测试、人工答案、独立对照和新环境运行。任何一项关键输出追不回规则与测试时,代码继续留在候选状态,不进入研究结果。