2. MATLAB工程项目的目录结构设计
很多MATLAB项目的起点都是一堆散落的脚本:test1.m、test1_new.m、test1_final.m、data.mat、result.png 全部堆在同一个文件夹里。当项目只有几十行代码时,这种方式勉强可用;一旦涉及多个模块、多组数据、多人协作或需要交付给他人复现,散落式组织就会迅速失控——路径写死、函数重名、数据被覆盖、结果无法追溯。工程化目录结构的核心目标,就是让“代码、数据、配置、结果、文档”各归其位,使项目可读、可跑、可交付。
标准目录树:五类资源分层
一个可交付的MATLAB工程,通常按资源类型划分为五个顶层目录,每个目录承担明确职责:
src/:源代码,存放函数、类、算法实现,是项目的核心资产;data/:输入数据,包括原始数据与预处理后的中间数据,建议再分raw/与processed/;config/:配置参数,如params.m、settings.json,把可变参数从代码中剥离;results/:输出结果,如图表、.mat结果文件、日志,按运行批次或日期归档;docs/:文档,包括说明、接口定义、交付清单。
此外,项目根目录放置入口脚本 main.m、项目说明 README.md 和版本控制文件 .gitignore。入口脚本只负责“装配”流程,不写具体算法,这样别人拿到项目后只需运行 main.m 即可复现。
路径管理:让项目“搬到哪都能跑”
散落脚本最典型的病症是绝对路径写死,例如 load('D:\work\data\exp1.mat')。一旦换机器或换目录,代码立刻报错。工程化做法是让入口脚本负责路径装配,业务函数只接收路径参数。下面是一段可直接参考的 main.m 骨架:
% main.m —— 入口脚本,只做装配,不写算法
projectRoot = fileparts(mfilename('fullpath'));
addpath(genpath(fullfile(projectRoot, 'src')));
cfg = loadConfig(fullfile(projectRoot, 'config', 'params.m'));
rawFile = fullfile(projectRoot, 'data', 'raw', 'exp1.csv');
outDir = fullfile(projectRoot, 'results', 'figures');
data = readExperiment(rawFile); % src/io
y = processSignal(data, cfg); % src/core
plotResult(y, cfg, outDir); % src/viz
rmpath(genpath(fullfile(projectRoot, 'src')));
这里的关键是 mfilename('fullpath'):它返回当前脚本的完整路径,再取 fileparts 得到项目根目录。无论项目被复制到哪台机器、哪个盘符,路径都能自动推导。业务函数如 readExperiment 只接收 rawFile 参数,不关心项目结构,从而与目录解耦。
资源分层与命名规范
资源分层解决“东西放哪”,命名规范解决“东西叫什么”。两者配合,才能让项目在半年后仍能被自己和他人读懂。命名建议遵循以下约定:
- 函数与脚本用小写加下划线:
read_experiment.m、process_signal.m; - 类名用大驼峰:
SignalProcessor.m; - 数据文件带日期或批次:
exp1_20240512.csv,避免data_new.mat这类模糊命名; - 结果文件与输入对应:
exp1_20240512_spectrum.png,便于追溯; - 配置项集中命名,如
cfg.fs、cfg.windowLen,不散落在函数内部。
一个可交付的目录结构示例
假设项目是“振动信号频谱分析”,按上述规范组织后,目录树如下:
VibAnalysis/
├── main.m
├── README.md
├── .gitignore
├── config/
│ └── params.m
├── data/
│ ├── raw/
│ │ └── exp1_20240512.csv
│ └── processed/
│ └── exp1_20240512_clean.mat
├── src/
│ ├── io/
│ │ └── read_experiment.m
│ ├── core/
│ │ └── process_signal.m
│ └── viz/
│ └── plot_result.m
├── results/
│ ├── figures/
│ │ └── exp1_20240512_spectrum.png
│ └── logs/
│ └── run_20240512.log
└── docs/
└── interface.md
这个结构下,main.m 运行后,输入来自 data/raw/,中间结果写入 data/processed/,图表输出到 results/figures/,日志记录到 results/logs/。任何人拿到项目,只需确认 data/raw/ 中有对应数据,运行 main.m,即可得到与 results/ 中一致的结果。若结果不一致,可对比日志中的参数与数据文件名,快速定位差异来源。
results/ 和 data/processed/ 提交到版本控制。它们属于运行产物,应由 .gitignore 排除;只保留 data/raw/ 中的原始数据或提供数据获取说明。
从散落到工程化的迁移路径
如果手上已有一个散落项目,不必推倒重来,可按以下顺序迁移:第一步,新建标准目录骨架;第二步,把纯函数移入 src/ 并按功能分子目录;第三步,把硬编码参数抽到 config/params.m;第四步,把数据文件按 raw/ 与 processed/ 分类;第五步,编写 main.m 串联流程,并用 mfilename 替换所有绝对路径;第六步,运行验证,确认输出与迁移前一致。迁移完成后,项目从“只能自己跑”变为“别人也能跑”,这是工程化交付的第一道门槛。
目录结构设计看似只是整理文件,实质是在定义项目的边界与契约:哪些是输入、哪些是输出、哪些可修改、哪些应冻结。把这件事做好,后续的版本管理、自动化测试和交付文档才有稳定的落脚点。