叠个甲:本人并非软件工程等计算机专业的学生,也没有参与过商业化的项目经历;本文使用了AI进行一些内容补充,但核心思路依然是手写的(甚至是从纸面上搬上电脑的);本人不认为自己能对别人指指点点,也从未认为自己平常搞点小研究的方法一定适合所有人和所有研究。以下内容请理性判断。

实验数据和业务数据,都是要处理的
做实验、写报告、处理数据——修过实验课或设计类课程的同学对这套流程肯定不陌生。
比如你在课程项目里采了一组传感器数据,得做这些处理:去异常值、平滑滤波、算统计量、画图、导出。第一次弄,你大概率会打开 Excel,手动筛选、排序、写几个公式。能用,但挺折磨——数据一多界面就卡,步骤一多漏一步就得全盘重来,改个参数基本等于重做。以及如果Excel用得不是很好,还要经常去搜。
更让人有时候不知所措的是,搞完第一组,发现还有四组格式类似但参数不同的数据在排队。这时候大概会想到:有没有啥更加自动化的操作?
这不是一个人的困境。几乎所有涉及数据的学科,不管物理、生物、化学、经济还是社科,都会走到这个路口:重复的事情,为什么不能交给机器呢?我们推进了这么久的自动化,怎么(在实验室里)还是在干赛博体力活(复制黏贴改参数)?工业和科研上的自动化情况相去甚远?
这里我们稍微扯一点“何为自动化”:工业自动化处理的是已知、标准化的输入;而实验数据往往是“非标”的,今天换个仪器格式,明天改个去噪阈值,科研数据不是工业流水线上的“标准件”。在过去,要把这种灵活的、非标的处理逻辑写成严谨的自动化脚本,门槛太高了。很多时候,写脚本和调Bug花的时间,比手动点鼠标处理完还要长。这就是为什么我们明知重复,却还在手动干体力活。
语言类大模型的出现可以打破这个僵局。它把“把模糊需求变成严谨代码”这个最难的门槛给踏平了。你不需要精通编程,只要用大白话告诉它需求,它就能给你生成脚本。于是,面对那四组排队的数据,我们可以让AI来帮助我们。
AI 很强,但别让它算数
大语言模型在写代码这件事上确实表现出色。跟它说"帮我写一个脚本,读取 CSV,去掉三倍标准差之外的异常值",它大概率能给出一段直接能跑的代码。
但LLM们其实不太会正儿八经“算数”:让它算 3847 × 2963,它可能给你一个看似合理但完全错误的答案。这是LLM的原理决定的:输出过程不是在"计算",而是在"预测下一个最可能的 token"(这一部分的详细内容如果感兴趣可以看一下临近的一篇,会稍微介绍一下)。对它来说,数学运算是模式匹配,就像在一张大表格里不断地找对应,不是确定性算术。这在大量数据多步运算中极其容易出错。直到这时,计算机先前的一个目的才又被人想起:计算。
我觉得比较适合的使用方式是:
让 AI 写代码,让代码去算数。
你描述需求,AI 生成处理脚本,你在本地运行并验证结果。
不过这里有一个容易被忽略的点:AI 降低了写代码的门槛,但没有降低写“对”代码的门槛。 生成的脚本运行起来未必完全符合你的预期,除了跑不通这样的大问题以外,更麻烦的问题是逻辑上有微妙的错误(在古法编程的时代也是这样的,逻辑错误才是更麻烦的问题)。所以一个合理的做法是:自己审读一遍逻辑,或者用另一个模型交叉审查,至少拿一组已知结果的数据做验证。这不是可选步骤,而是数据处理流程中必要的质量关卡,如果这个工具产生的结果都是错误的,那它将在后续的处理过程中带来巨大的麻烦。
一旦开始写脚本处理数据,你就不再是一个手动操作 Excel 的实验者了——你开始成为一个用工具管理流程的人。
从 Excel 到脚本:迈向更强大的自动化
大多数人的数据处理能力进化路径是这样的:
- 手动操作。 Excel 里筛选、排序、写公式。直观,但脆弱——改一个步骤,后面全部白费。
- 脚本化。 用 Python 或类似工具把操作写成代码。一次编写,反复运行。换一组数据?改一下文件路径就行。
- 工程化。 脚本越来越多,开始考虑模块化、可读性、版本管理。
这里有一些我知道的脚本方法,欢迎各位补充捏
Python
如果有不依靠vibe coding的开发经验,Python 应该是最顺手的选择。数据处理生态成熟——pandas、numpy、matplotlib 三件套几乎覆盖了所有常见需求(scipy好),AI 对它的代码生成质量也最高。
痛点是包管理。Python 的依赖管理长期是个老大难:pip 和 conda 的冲突、虚拟环境的配置、版本锁定的缺失。新项目配环境花半小时,改完代码跑不了才发现少装了一个包,这种事几乎每个 Python 开发者都经历过。好在近几年有所改善——uv、poetry、pyproject.toml 等工具在逐步收敛这个问题,但它依然是你选 Python 时需要接受的一个小摩擦。
Julia
专为科学计算诞生的语言,求解偏微分方程组的速度比C++还快。但我不太会,没准会在博客里新开一个坑的。
Rust等其他编程语言
其实是个编程语言应该都能写脚本(包括Powershell)。Rust有极高的运行效率和内存安全等一系列良好的特性,然而最大的问题是,我没有本事跑通写出来的脚本(
MATLAB
MATLAB 在工程和科研领域依然是主流工具之一。优势很明显:非常成熟,不需要折腾包管理,数值计算和可视化都有成熟的内置支持。你跟 AI 说"帮我写一个 MATLAB 脚本",它同样能生成质量不错的代码。
限制在于它是商业软件,需要授权。神通广大的读者同志们应该还是能解决的。
Mathematica
Mathematica 在符号计算和数学推导方面几乎无可替代。AI 对它的支持也不错,但有一个额外门槛——如果你想在命令行环境中调用 Mathematica 内核执行计算,需要通过 wolframscript 这样的工具,配置过程比 Python 曲折一些。你需要明确告诉 AI 具体的调用方式,否则它可能给你一个只在 Mathematica 前端里才能运行的答案。
关于Scilab这类小众工具的一点补充
如果喜欢用 Scilab 这类小众工具(比如我),往往需要面对一个尴尬的现实:AI 生成的代码质量高度依赖语言生态的成熟度。 Python 之所以好用,不仅因为它本身设计合理,更因为训练数据里有海量的 Python 代码和问答,形成了正循环——用的人多 → AI 训练数据多 → 生成质量高 → 更多人愿意用。小众工具的问题是:功能不差,但 AI 写不好,一边跑通的难度比较大。(类似的情况还有typst,和的语法总有些差距,即使是让AI先看文档,也很难一次写对)。
这本身就是一个值得纳入考量的工程化决策:选工具不仅看功能,还要看工具链的完整性和生态成熟度。
怎么选
我个人很主观的的建议是:如果已经有一定的编程基础,就用 Python,把环境管理当成一个小成本的必修课;如果只做简单的数值计算和画图,MATLAB 是阻力最小的路径;Mathematica 更适合明确需要符号计算的时候。上述的几种方案都需要去编写代码。但应该注意的是,不一个个让AI的兴起带来基础能力的空心化,绝对应该避免许愿式编程。看懂代码、分析一个算法的合理性,仍然是重要且不可缺失的核心能力。否则,任何AI的产出对我们而言,都将成为一个黑箱。
不管选啥,我们的数据处理流程都应该发生变化:"人在GUI(Graphical User Interface,图形用户界面)里点击"->"代码定义逻辑、机器执行计算"。
寻找重复表象之下的抽象实质
处理完第一组数据之后,处理第二组的体验往往是这样的:打开之前写好的脚本,改几个参数,跑一遍,结果出来了。然后第三组、第四组……每组数据的处理流程几乎是同一套逻辑的不同实例。马克思主义基本原理概论里已经说了,人可以认识事物背后的抽象的联系,所以我们也可以试着试着找出一套抽象的共用逻辑。
这里其实有一个实际的效率和风险问题。如果三组数据的去噪逻辑写在三个不同的地方,那么一旦发现去噪算法本身有 bug,就需要改三个地方。漏改一个,结果就不一致。数据集越多,维护成本越高,出错的概率也越大。
DRY 原则
软件工程里有一个原则:DRY——Don't Repeat Yourself。 不要重复自己。
核心思想很简单:如果一段逻辑出现了两次以上,就应该把它提取出来,变成一个可以反复调用的单元。在编程中,这个单元往往就是函数。
举一个科研场景中的例子。假设数据处理流程包含以下步骤:
- 读取数据文件
- 去除异常值(基于三倍标准差)
- 归一化(减均值除标准差)
- 计算统计量
- 生成图表
最初的脚本可能是一个从头跑到尾的线性流程,所有步骤写在一个文件里。当需要用同样的流程处理第二组数据时,最常见的做法是复制整个文件,改一下输入路径和参数。
更好的做法是把每一步封装成函数:
def load_data(path):
# 读取数据
...
def remove_outliers(data, threshold=3):
# 去除异常值
...
def normalize(data):
# 归一化
...
def compute_stats(data):
# 计算统计量
...
def plot_results(data, stats):
# 生成图表
...主流程变成了一行行清晰的调用:
raw = load_data("dataset_01.csv")
cleaned = remove_outliers(raw, threshold=2.5)
normalized = normalize(cleaned)
stats = compute_stats(normalized)
plot_results(normalized, stats)处理第二组数据时,只需要换一下输入文件和参数,整个流程不需要复制。更重要的是,如果发现去噪逻辑需要修正,只需要改 remove_outliers 这一个函数,所有使用它的流程都会自动受益。
关注点分离
函数化带来的另一个好处是关注点分离——每个函数只负责一件事。
线性脚本的问题在于,读数据、清洗、计算、画图的逻辑全部混在一起。当脚本变长(几十行变成几百行),想找某个具体步骤的逻辑就很困难。调试的时候也不知道是哪个环节出了问题。
拆成函数之后,每个函数是一个独立的单元,可以单独测试、单独理解。想改画图风格?直接去找 plot_results。想换归一化方法?只看 normalize。认知负担大幅降低。
这在科研场景中尤其有用。很多时候数据处理的流程本身并不复杂,真正让人头疼的是管理复杂度——几十个步骤、多组参数、多种数据格式混在一起,如果没有任何结构化管理,就会变成一团混乱的意大利面代码。
当函数不够用的时候
函数适合封装"一段操作",但有时候需要封装的是"一组相关的数据和操作"。
比如在处理实验数据时,可能需要同时管理原始数据、清洗后的数据、计算结果、元信息(实验参数、时间戳、操作者等)。把这些东西散落在各个变量里会很乱,把它们打包成一个对象会更清晰:
class ExperimentResult:
def __init__(self, raw_data, params):
self.raw = raw_data
self.params = params
self.cleaned = None
self.stats = None
def clean(self, threshold=3):
self.cleaned = remove_outliers(self.raw, threshold)
def analyze(self):
self.stats = compute_stats(self.cleaned)
def summary(self):
print(f"参数: {self.params}")
print(f"样本量: {len(self.raw)}")
print(f"均值: {self.stats['mean']:.4f}")这有点面向对象的编程方式再里面,把数据打包带走。当然并不是必须的,实际上很多面向过程的编程思路在处理实验数据的时候做的很好。
抽象的代价
当然,抽象不是没有代价的。
我再也不搞抽象了,一来没有人理解我的幽默,二来真的有人把我当傻*
过度抽象会让代码变得难以理解——写了一层又一层的函数调用,回头一看自己都看不懂。对于只用一次的简单脚本,直接写成线性流程反而更清晰。经常也需要为不同的数据输入方式(不同维度的数组等等)分别进行一些准备,bug写着写着就这么出来了。
抽象的价值在复用频率和项目规模提升时才会显现。如果数据处理流程只会用一次、只涉及几百行代码,函数化可能没那么迫切。但如果要处理多组数据、流程包含十几个步骤、后续还会持续修改,那么在早期花一点时间做抽象,后期会节省大量维护成本。
判断标准其实很简单:当你发现自己在复制粘贴代码的时候,就该考虑抽象了。
随着脚本和函数越来越多,一个新的问题开始出现:代码改了,之前跑出来的结果还能复现吗?(你的实验数据数据复现不出来.jpg)三个月前用的参数和现在用的是同一版吗?
不再乞求15分钟前的自己
脚本写好了,函数封装好了,数据处理流程跑通了。接下来的日子里,一切看起来很顺利——直到有一天需要修改某个处理步骤。
改完之后,跑出来的结果和之前不一样了。是改对了,还是引入了新 bug?更糟糕的是,已经记不清改了什么、什么时候改的、为什么要改。
如果只有一两个人在做,这已经够头疼了。如果是团队协作,情况会更复杂——两个人各自改了不同部分,合在一起发现冲突了;某个人的改动覆盖了之前的版本,想回去找发现没有备份。
这些问题,软件工程领域早在几十年前就遇到过,并且给出了一套成熟的解决方案:版本控制。
什么是版本控制
版本控制的核心思想是简单的:记录每一次变更,并且可以随时回到过去的任何一个状态。
最广泛使用的版本控制工具是 Git。它的基本工作逻辑是:
- 在项目目录下初始化一个仓库(
git init) - 每完成一个阶段性的修改,做一次"提交"(commit),附带一句简短的说明
- Git 会保存这次提交的完整快照
- 需要的时候,可以查看历史记录、对比差异、回退到任意一次提交
git init
git add .
git commit -m "完成第一版去噪和归一化流程"
# ... 做了一些修改 ...
git add .
git commit -m "将去噪阈值从3调整为2.5"
# ... 发现改错了 ...
git log # 查看提交历史
git diff abc1234 def5678 # 对比两次提交的差异
git checkout abc1234 # 回到修改之前的版本对于科研工作者来说,最直接的好处是两个:
-
可追溯。 每次修改都有记录,随时可以回答"这个参数是什么时候改的、为什么改的"。
-
可回退。 改出问题的时候,不需要慌慌张张地手动恢复,一条命令就能回到之前的正常状态。
版本控制不止可以控制数据处理的代码,还可以控制其他常用的,比如:
-
论文草稿。 写论文时反复修改,某些被删掉的段落后来又想用回来。如果用 Git 管理 LaTeX 源文件,这些都可以轻松回溯。(注:word文档不行,或者说大部分基于xml的富文本格式都不太行,其文件本身中包含了太多二进制的内容,以至于在不完成回溯操作的前提下,大概人自己也看不懂这个版本里有啥。)
-
实验记录。每次实验的配置参数、运行环境、结果输出,作为版本历史的一部分一起提交,三个月后复现时不会一头雾水。
在科研场景中,版本控制解决的核心问题是可复现性:任何人、在任何时间点,都能拿到完全一致的代码、数据和配置,得到完全一致的结果。这在团队协作中是刚需,在个人项目中也同样重要——因为"任何人"也包括三个月后的自己。
一些实际的建议
一般来说,我们这些不完全靠计算机吃饭的不需要掌握 Git 的所有高级功能。以下几条足以应对大多数场景:
-
从小处开始。 不需要一开始就搭建复杂的分支管理策略。哪怕只是在一个文件夹里
git init,然后定期commit,就已经比没有任何版本管理强了太多。 -
提交信息要有意义。
fix bug和把去噪阈值从3改为2.5,原因是原始阈值导致过多有效数据被丢弃之间的信息量差距巨大。后者在三个月后依然能看懂。 -
保持文件名简洁。 文件名只要能让人看懂他在干啥就行,版本信息交给 Git。
-
代码和文档一起提交。 在仓库里放一个
README.md(因为Github认这个,当然写个开发/研究日记也可以),简单记录项目的用途、依赖、运行方式。未来回看时,这个文档会成为最值钱的东西之一。
版本控制补上了工程化思维链条中"管理变更"这一环。到这里,从 Excel 到脚本、从脚本到抽象封装、从封装到版本管理,一条完整的进化路径已经呈现。但这些具体的技术手段背后,贯穿始终的是一种更底层的思维方式。
工程化思维到底是什么
回顾前面几章,我们经历了一条这样的路径:
手动操作 → 脚本化 → 抽象复用 → 版本管理
每一步的驱动力都是非常朴素的需求:事情变复杂了,原来的办法撑不住了。
数据量大了,Excel 卡了,所以写脚本。流程重复了,复制粘贴太麻烦而且容易漏,所以抽象成函数。改了代码不知道之前的结果还能不能跑,所以用版本控制。
这些具体的技术选择背后,有几个反复出现的共同特征:
-
可复现。 同样的输入得到同样的输出。过程和结果都可以被验证、被追溯。这不仅是一个技术要求,更是一种对自身工作的负责态度。(也是被查论文的保护措施(吗?))
-
可维护。 代码和流程是被人阅读和修改的,不只是被机器执行的。清晰的结构、有意义的命名、必要的注释,都是在为未来的自己(和协作者,比如你未来的师弟师妹们)降低认知成本。
-
可扩展。 初始方案不需要完美,但结构上要留有余地,让后续的修改不至于推倒重来。
这三个特征,就是工程化思维的核心。它不是某一种工具、某一种语言、某一套流程,而是一种对待工作的态度——不只关注"这件事怎么做完",也关注"做完之后,这件事能不能被理解、被复用、被改进"。
这种思维方式在软件工程中是基本功,在科研中同样有价值。科研的核心是探索和发现,但探索的过程也需要被记录、被验证、被他人理解。(经常写研究报告的)同学们都知道写研究报告的时候,还要费尽心思地去回忆自己干了啥是件多少令人头大的事情。工程化思维不会替代科研思维中的直觉、创造力和判断力,而是为这些能力提供一个可靠的基础设施。
在这个 AI 能辅助编写代码的时代,写代码的门槛在降低,但写对的代码、写可维护的代码、写可复现的代码——这些能力的重要性反而在上升。工程化思维不会因为 AI 的出现而过时,相反,它会成为区分"能用 AI 写代码"和"能用 AI 写好代码"的关键分水岭。处在海量数据中的我们,也可以试着用工程化的思维,改善一下我们的数据处理过程。
后记:开源、模拟平台与科研工具的“孤岛”
如果和我一样经常在网上逛稍微技术一点的地方,可能会有一种强烈的割裂感:在计算机领域,GitHub 上每天都有成千上万个新轮子诞生,各种框架、库、脚手架多如牛毛,Blender和Inkscape之类的杰出开源软件代表甚至可以满足诸多用户是生产级需求,Linux操作系统本身也是几乎奇迹式的存在。
但一回到我们基础科学(物理、化学、力学、材料等)的模拟、研究领域,画风突变:开源工具数量稀少,文档残缺不全,很多课题组还在用着几十年前的祖传代码,而且这些代码没准还是跑在许久之前买的商业软件上的。ANSYS, COMSOL 这类软件大厂的黑盒我们得一直用。但前些年Matlab对我国部分高校的制裁,提醒我们回到电脑前,想一想国产CAD(Computer Aided Design,计算机辅助设计)、EDA(Electronic Design Automation,电子设计自动化)、FEM(Finite Element Method,有限元)和CAE(Computer Aided Engineering)软件的未来在哪里。
我觉得这里有研究对象的不同:计算机科学处理的对象是“信息”和“逻辑”,这些是高度抽象的。一个 Web 框架、一个数据库、一个机器学习库,写出来之后,无论是做电商还是做社交都能用,复用率极高。 但基础科学研究的是“具体的自然规律”。流体力学要解 Navier-Stokes 方程,分子动力学要算 Lennard-Jones 势,量子力学要解Schrödinger方程。这些工具(比如 Elmer、OpenFOAM、GROMACS)往往是为了解决某一类极其具体的物理问题而诞生的。它们天然就是“领域特定”的,通用性弱,受众面窄,且编写门槛极高,往往需要足够强大的数理基础,而能写这个人大概也比较忙,自然很难像计算机工具那样形成庞大的通用生态。
工科的情况有时也类似,甚至更加:理科的同志们遇到的方程可能还是类似的,工科更偏向工程模拟的软件往往面对不同的细分、具体的场景。SolidWorks, AutoCAD, CATIA,同样是建模,不同的行业细分有自己的选择。包括NASA和SpaceX的设计师们,用的往往也是小众的内部软件。当工具必须死死贴合特定的工程需求时,开源社区那种“做通用轮子”的模式就失效了。
科研的数字化工具生态,大概不会像互联网软件那样百花齐放。但工程化的思维——模块化设计、接口标准化、版本控制、持续集成——依然可以帮助我们站在巨人的肩膀上,而不是在各自的孤岛上重复造轮子。OpenFOAM 的贡献者生态、FEniCS 的自动化求解器生成、甚至 Jupyter 在科研计算中的普及,都证明了即使在“领域特定”的泥潭中,开源协作依然能开出花来。一篇论文发表时附上源码和数据,不应该是一种奢望,而应该成为学术共同体的默契。当代码和论文一起被引用、被继承、被改进,这些“小众工具”就不再是易失的草稿,而会成为可累积的学术资产。到那时,基础科学的数字化基础设施,或许不会像 GitHub 那样喧嚣,但它会像科学本身一样——缓慢、扎实、经得起时间的检验。