浅谈工程化思维在科研探索开发中的可能性

想法researchdevelopment7/11/20266643 字33 分钟

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

cover

实验数据和业务数据,都是要处理的

做实验、写报告、处理数据——修过实验课或设计类课程的同学对这套流程肯定不陌生。
比如你在课程项目里采了一组传感器数据,得做这些处理:去异常值、平滑滤波、算统计量、画图、导出。第一次弄,你大概率会打开 Excel,手动筛选、排序、写几个公式。能用,但挺折磨——数据一多界面就卡,步骤一多漏一步就得全盘重来,改个参数基本等于重做。以及如果Excel用得不是很好,还要经常去搜。

更让人有时候不知所措的是,搞完第一组,发现还有四组格式类似但参数不同的数据在排队。这时候大概会想到:有没有啥更加自动化的操作?

这不是一个人的困境。几乎所有涉及数据的学科,不管物理、生物、化学、经济还是社科,都会走到这个路口:重复的事情,为什么不能交给机器呢?我们推进了这么久的自动化,怎么(在实验室里)还是在干赛博体力活(复制黏贴改参数)?工业和科研上的自动化情况相去甚远?

这里我们稍微扯一点“何为自动化”:工业自动化处理的是已知、标准化的输入;而实验数据往往是“非标”的,今天换个仪器格式,明天改个去噪阈值,科研数据不是工业流水线上的“标准件”。在过去,要把这种灵活的、非标的处理逻辑写成严谨的自动化脚本,门槛太高了。很多时候,写脚本和调Bug花的时间,比手动点鼠标处理完还要长。这就是为什么我们明知重复,却还在手动干体力活。

语言类大模型的出现可以打破这个僵局。它把“把模糊需求变成严谨代码”这个最难的门槛给踏平了。你不需要精通编程,只要用大白话告诉它需求,它就能给你生成脚本。于是,面对那四组排队的数据,我们可以让AI来帮助我们。

AI 很强,但别让它算数

大语言模型在写代码这件事上确实表现出色。跟它说"帮我写一个脚本,读取 CSV,去掉三倍标准差之外的异常值",它大概率能给出一段直接能跑的代码。

但LLM们其实不太会正儿八经“算数”:让它算 3847 × 2963,它可能给你一个看似合理但完全错误的答案。这是LLM的原理决定的:输出过程不是在"计算",而是在"预测下一个最可能的 token"(这一部分的详细内容如果感兴趣可以看一下临近的一篇,会稍微介绍一下)。对它来说,数学运算是模式匹配,就像在一张大表格里不断地找对应,不是确定性算术。这在大量数据多步运算中极其容易出错。直到这时,计算机先前的一个目的才又被人想起:计算。

我觉得比较适合的使用方式是:

让 AI 写代码,让代码去算数。

你描述需求,AI 生成处理脚本,你在本地运行并验证结果。

不过这里有一个容易被忽略的点:AI 降低了写代码的门槛,但没有降低写“对”代码的门槛。 生成的脚本运行起来未必完全符合你的预期,除了跑不通这样的大问题以外,更麻烦的问题是逻辑上有微妙的错误(在古法编程的时代也是这样的,逻辑错误才是更麻烦的问题)。所以一个合理的做法是:自己审读一遍逻辑,或者用另一个模型交叉审查,至少拿一组已知结果的数据做验证。这不是可选步骤,而是数据处理流程中必要的质量关卡,如果这个工具产生的结果都是错误的,那它将在后续的处理过程中带来巨大的麻烦。

一旦开始写脚本处理数据,你就不再是一个手动操作 Excel 的实验者了——你开始成为一个用工具管理流程的人。

从 Excel 到脚本:迈向更强大的自动化

大多数人的数据处理能力进化路径是这样的:

  1. 手动操作。 Excel 里筛选、排序、写公式。直观,但脆弱——改一个步骤,后面全部白费。
  2. 脚本化。 用 Python 或类似工具把操作写成代码。一次编写,反复运行。换一组数据?改一下文件路径就行。
  3. 工程化。 脚本越来越多,开始考虑模块化、可读性、版本管理。

这里有一些我知道的脚本方法,欢迎各位补充捏

Python

如果有不依靠vibe coding的开发经验,Python 应该是最顺手的选择。数据处理生态成熟——pandas、numpy、matplotlib 三件套几乎覆盖了所有常见需求(scipy好),AI 对它的代码生成质量也最高。

痛点是包管理。Python 的依赖管理长期是个老大难:pip 和 conda 的冲突、虚拟环境的配置、版本锁定的缺失。新项目配环境花半小时,改完代码跑不了才发现少装了一个包,这种事几乎每个 Python 开发者都经历过。好在近几年有所改善——uvpoetrypyproject.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,和LaTeX\LaTeX的语法总有些差距,即使是让AI先看文档,也很难一次写对)。

这本身就是一个值得纳入考量的工程化决策:选工具不仅看功能,还要看工具链的完整性和生态成熟度。

怎么选

我个人很主观的的建议是:如果已经有一定的编程基础,就用 Python,把环境管理当成一个小成本的必修课;如果只做简单的数值计算和画图,MATLAB 是阻力最小的路径;Mathematica 更适合明确需要符号计算的时候。上述的几种方案都需要去编写代码。但应该注意的是,不一个个让AI的兴起带来基础能力的空心化,绝对应该避免许愿式编程。看懂代码、分析一个算法的合理性,仍然是重要且不可缺失的核心能力。否则,任何AI的产出对我们而言,都将成为一个黑箱。

不管选啥,我们的数据处理流程都应该发生变化:"人在GUI(Graphical User Interface,图形用户界面)里点击"->"代码定义逻辑、机器执行计算"。

寻找重复表象之下的抽象实质

处理完第一组数据之后,处理第二组的体验往往是这样的:打开之前写好的脚本,改几个参数,跑一遍,结果出来了。然后第三组、第四组……每组数据的处理流程几乎是同一套逻辑的不同实例。马克思主义基本原理概论里已经说了,人可以认识事物背后的抽象的联系,所以我们也可以试着试着找出一套抽象的共用逻辑。

这里其实有一个实际的效率和风险问题。如果三组数据的去噪逻辑写在三个不同的地方,那么一旦发现去噪算法本身有 bug,就需要改三个地方。漏改一个,结果就不一致。数据集越多,维护成本越高,出错的概率也越大。

DRY 原则

软件工程里有一个原则:DRY——Don't Repeat Yourself。 不要重复自己。

核心思想很简单:如果一段逻辑出现了两次以上,就应该把它提取出来,变成一个可以反复调用的单元。在编程中,这个单元往往就是函数。

举一个科研场景中的例子。假设数据处理流程包含以下步骤:

  1. 读取数据文件
  2. 去除异常值(基于三倍标准差)
  3. 归一化(减均值除标准差)
  4. 计算统计量
  5. 生成图表

最初的脚本可能是一个从头跑到尾的线性流程,所有步骤写在一个文件里。当需要用同样的流程处理第二组数据时,最常见的做法是复制整个文件,改一下输入路径和参数。

更好的做法是把每一步封装成函数:

python
def load_data(path):
    # 读取数据
    ...

def remove_outliers(data, threshold=3):
    # 去除异常值
    ...

def normalize(data):
    # 归一化
    ...

def compute_stats(data):
    # 计算统计量
    ...

def plot_results(data, stats):
    # 生成图表
    ...

主流程变成了一行行清晰的调用:

python
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。认知负担大幅降低。

这在科研场景中尤其有用。很多时候数据处理的流程本身并不复杂,真正让人头疼的是管理复杂度——几十个步骤、多组参数、多种数据格式混在一起,如果没有任何结构化管理,就会变成一团混乱的意大利面代码。

当函数不够用的时候

函数适合封装"一段操作",但有时候需要封装的是"一组相关的数据和操作"。

比如在处理实验数据时,可能需要同时管理原始数据、清洗后的数据、计算结果、元信息(实验参数、时间戳、操作者等)。把这些东西散落在各个变量里会很乱,把它们打包成一个对象会更清晰:

python
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。它的基本工作逻辑是:

  1. 在项目目录下初始化一个仓库(git init
  2. 每完成一个阶段性的修改,做一次"提交"(commit),附带一句简短的说明
  3. Git 会保存这次提交的完整快照
  4. 需要的时候,可以查看历史记录、对比差异、回退到任意一次提交
bash
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 那样喧嚣,但它会像科学本身一样——缓慢、扎实、经得起时间的检验。

文章标题:浅谈工程化思维在科研探索开发中的可能性

文章作者:SituChengxiang

文章链接:https://situchengxiang.pages.dev/posts/2026-07-11-scientific-engineering[复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。