刚接手某项目时,我拿到一份没有注释的Matlab脚本,函数名全是m_1、s_2这种自动生成的乱码,文件顶层写着几句话,下面就是密密麻麻的矩阵运算。我花了一下午,才从变量名Xf和Xp猜出这是在跑卡尔曼滤波,又花了两天才敢动手改。从那天起,我就养成了一个习惯:新写的每个Matlab脚本和函数,第一件事不是敲代码,而是先把注释骨架搭好。这篇东西,就是我在长期“注释自救”过程中整理出来的方法,包含Matlab注释规范、自定义模板和一些能直接用的实操套路,适合所有写Matlab的人参考,尤其是做算法验证、信号处理和数据计算的工程师。
1. 先搞清楚注释到底给谁看:三个真实场景
1.1 三个月后的“你”,就是最需要注释的人
很多人写注释是为了应付公司规范或导师检查,但真正让你后悔没写注释的,往往是三个月后的自己。我自己的经验是:Matlab项目通常不是一口气写完的,往往是今天跑通一个模块,明天调一个参数,后天加一个功能。三天内你可能还能记住data_filt是什么,三十天后就完全不一样了。
有一回我处理一批加速度传感器数据,写了段带通滤波代码,当时觉得逻辑很简单,就没怎么注释。三周后我回头想改一下通带频率,看着那段用butter、filtfilt组合起来的代码,脑子里反复出现的问题就是:这个截止频率是按Hz还是rad/s算的?Wn归一化的时候除的采样率是多少?结果我不得不把前前后后的文档全部翻出来,才确认当初的设计。如果当时写一句“% 通带 5-50 Hz,Fs = 1000 Hz,归一化截止频率 = [5,50]/(Fs/2)”,三秒钟就能解决的事,硬生生花了二十分钟。
注释的第一价值,不是给别人看的,是给未来的自己看的。
1.2 接手代码的人,不会替你脑补设计思路
团队协作时,没有注释的代码就是一颗定时炸弹。尤其Matlab这类偏工具型的语言,很多工程师习惯把脚本写完就跑,认为“能出结果就行”,但项目一旦交接,问题就来了。
我记得某公司同事接手过一套无人机的姿态解算代码,整套代码用四元数写,注释少得可怜。文件中到处都是q = q * dq这种公式,负责交接的老人已经离职,留下的文档只写着“使用四元数更新姿态”。接手的人只能从网上搜到四元数基本公式,自己一行行对公式、猜变量,原定两天的联调硬是拖了一周。这里面真正缺的不是数学知识,缺的是“某段代码对应某个公式、变量符号取自哪本书”这类注释。
所以我一直强调:写注释时,可以多想一想“如果这个文件交给一个完全没参与项目的人读,TA会遇到哪些障碍”。帮助读者建立起代码和需求之间的联系,比重复代码逻辑重要得多。
1.3 评审和答辩的场景里,注释就是你的“讲解稿”
这个场景在院校和科研单位尤其突出。Matlab代码经常作为毕业设计、课题结题或者论文复现的一部分提交,评审人不会打开.m文件一行行看,但会直接找一个关键函数去看你写了什么。如果你的函数没有帮助文本,没有输入输出说明,评审人会直接判断“这件事没做完整”。
有一个很典型的细节:Matlab自带的lookfor命令会索引所有函数注释中的第一行(H1行),比如你写了一个% 计算轴承故障特征频率,用lookfor 轴承就能搜到你的函数。这本身就是一种“注释带来的可检索性”——不但人看得懂,MATLAB的工具也看得懂。我在几次项目检查中,就是靠把这些细节补齐,让代码直接可被搜索、可被评估,省了很多口头解释。
2. 官方文档风格注释:从函数帮助文本到发布格式
2.1 函数帮助文本的结构化写法
Matlab在说明文档里长期推荐一种“函数帮助文本”写法,简单来说就是:所有.m函数文件顶部的整段注释,既给人看,也给help命令和lookfor命令看。
一个标准的函数帮助文本通常长这样:
function y = lowpassFilter(x, Fs, cutoffFreq) %LOWPASSFILTER 对输入信号执行低通滤波 % 使用Chebyshev II型滤波器对信号x进行低通滤波。 % % 语法: % y = lowpassFilter(x, Fs, cutoffFreq) % y = lowpassFilter(x, Fs, cutoffFreq, order) % % 输入参数: % x - 输入信号,一维向量 % Fs - 采样率,单位为Hz % cutoffFreq - 截止频率,单位为Hz % order - 可选参数,滤波器阶数,默认值为4 % % 输出参数: % y - 滤波后的信号,维度与输入x一致 % % 示例: % >> t = 0:0.001:1; % >> y = lowpassFilter(sin(2*pi*50*t), 1000, 20); % % 参见: % BUTTER, CHEBY2, FILTFILT % % 作者:某开发者 % 日期:2025年11月12日 % 版本:1.2这段注释有几点值得注意。第一行%LOWPASSFILTER会让help lowpassFilter在窗口里把这句话加粗显示,lookfor也能把这段内容抓进索引。中间“语法、输入参数、输出参数、示例、参见”这种分节,本质上是模仿官方文档的简版,对于任何规模的函数都适用。加上作者、日期、版本,就成了一个最简单的自定义注释模板。
我在实际使用中发现,把“输入输出说明”写清楚,最大的受益者是函数调用方,也就是大概率还是你自己。很多函数写完之后隔一段时间再用,看到参数说明就知道该传什么进来,不用再去翻源代码。
2.2 利用%%注释生成可发布文档
Matlab编辑器里有一个非常常见但常被低估的功能:发布(Publish)。它的原理就是读取脚本中的注释,按格式生成HTML、PDF或Markdown文档。只要在代码中插入%%作为章节标题,发布出来的文档就会自动生成目录,注释里的普通文字会被当作文本输出。
举个例子,下面这种写法:
%% 数据加载 % 从CSV文件中读取原始振动信号,并做初步检查。 data = readmatrix('vibration_data.csv'); %% 数据预处理 % 去除均值,消除直流分量。 data = data - mean(data); %% 频域分析 % 做快速傅里叶变换,并绘制幅值谱。 ...发布后,这三段注释就是文档的三个章节,代码块、图像会被自动嵌入。这种玩法等于“一份代码同时是文档”,特别适合做数据分析和算法验证类项目。我对这个功能的评价是:它让“注释规范”从纯书写习惯升级成了一种文档生成手段,你的注释水平直接决定文档质量,所以会更认真去写。
| 发布用注释符号 | 作用 | 说明 |
|---|---|---|
%% 章节标题 | 生成文档中的一级章节 | 前面有空格也会识别 |
% 普通文本 | 在文档中显示为普通文字 | 以%开头且非%%的内容 |
%% 内嵌公式 | 支持LaTeX形式的数学公式 | 发布时在注释中用$...$写公式 |
% 图片标题 | 为紧随其后生成的图像添加标题 | 图像会自动收集 |
我在做某信号处理Demo时,就靠发布功能直接生成了实验报告,省去单独写文档的时间。这套玩法的前提就是注释写得规范。
3. 普通注释的分寸:块注释、行内注释与特殊标记的使用边界
3.1 三种语法,各自用在哪
Matlab里有三种注释形式:以%开头的整行注释,放在代码后面的%行尾注释,以及%{和%}包裹的注释块。很多人只会用第一种,容易把长段解释写在代码行之间,看起来碎碎的。实际项目中,这三种各有分工。
整行注释适合给一个逻辑块做总说明。我在每个算法段落前面都会写一两句“这个段落的作用是什么”“为什么放在这里”。比如:
% 计算状态转移矩阵,利用离散化近似 F = eye(4) + dt * A;这段注释解释了公式的来源。行尾注释适合给一个具体变量或一行代码做提示,比如% units: m/s、% index starts from 1,点到即可。注释块则适合放一整段推导说明、参考文献摘录或者较长算法的思路说明,用%{ ... %}包起来,代码结构不会被大量%打断。
3.2 TODO、FIXME、NOTE 这些标记怎么用才不乱
在代码里写TODO、FIXME、NOTE其实是个常见习惯,但很多人用得很随意,标得到处都是,最后成了另一种噪音。我自己的约定是:
TODO:表示还有功能没实现或需要优化,必须配上日期和负责人。FIXME:表示这里已知有问题,但暂时不能或不便修复。NOTE:表示这里有一个重要细节,调用方或维护者必须知道。HACK:表示此处是临时方案,以后需要替换。
在Matlab编辑器里,这些标记默认会有颜色提示,看起来很明显。举个例子:
% TODO(2025-11-12): 将插值方法从线性改为样条 % FIXME: 当前处理的边界条件在超过3个点时可能不成立这样写,搜索TODO就能把所有未完成事项拉出来。自己维护个人项目时,这种标记比任务清单还可靠,因为它是钉在代码边上的,不会丢失上下文。
3.3 行内注释的“少而精”原则
我见过最夸张的代码,几乎每行都写注释,像“% 加1循环”、“% 取绝对值”。这种注释对理解代码毫无帮助,反而打断思路。真正值得用行内注释的,是那些“看代码本身看不出来的信息”。
比如这段:
b = fir1(order, Wn); % Wn已经按Fs/2归一化,切勿再次归一化这行代码虽然是注释,但它要传达的是一条“不要踩坑”的信息,非常有用。再比如:
h = waitbar(0, '处理中'); % 不用drawnow,waitbar会自动刷新这是经验。行内注释应该回答“为什么这样做”,而不是“做了什么”。这个原则我几乎每次都写进自定义注释规范里。
4. 自定义一套适合自己的注释规范:设计思路与示例模板
4.1 从“约定”到“规范”,需要覆盖哪些要素
很多团队有“多写注释”这个约定,但执行效果很差,因为太含糊。真正的规范要回答:什么地方必须写注释、写成什么格式、写到什么程度、如何检查。我设计自定义规范时,习惯按下面这组问题来组织,这套问题对个人项目同样适用:
- 文件头写不写?写哪些字段?我建议至少包括:文件名、功能简述、作者、日期、版本、修改记录。大型项目还可以加“依赖文件”和“运行环境”。
- 函数头写不写?写哪些节?我建议保留:功能一句话、语法、输入、输出、示例。可选:算法来源、参考文档。
- 算法段落的注释要求是什么?掺杂了公式的地方,建议把公式或参考文献直接写进注释。
- 特殊标记有哪些?统一使用哪些关键词?
- 修改代码时如何留痕?我建议使用
% 修改记录段,而不是直接删掉旧代码留一堆“尸体”。
规范一定不能太长。我对组内同学的建议就是:一页A4纸能写完,否则没人会看。最好的做法是先给模板,让大家照着模板填。
4.2 一套可直接复制的文件头模板和函数模板
下面这套是我个人项目里实际在用的模板。文件头比较简洁,重点在“为什么写这一步”;函数模板则采用“帮助文本 + 分节注释”的双层结构。
文件头模板:
% ============================================================== % 文件名:batchWindowing.m % 功能 :将原始振动信号按固定窗长分割为若干段,并生成时间索引 % 作者 :某开发者 % 日期 :2025-10-11 % 版本 :1.0 % -------------------------------------------------------------- % 修改记录: % 2025-10-25 v1.1 增加重叠率参数,避免窗边界信号截断 % 2025-11-02 v1.2 修复时间戳偏移,输出列改为与输入一致 % -------------------------------------------------------------- % 依赖 :无 % 运行 :MATLAB R2023a 及以上 % ==============================================================这套文件头的好处是:打开文件就能掌握全部背景信息,多年的修改历史全在顶部,不用去翻Git日志。很多项目用Git管理后就不写修改记录了,但我仍然保留这个习惯,因为在IDE里直接看文件头比敲命令查历史快得多。
算法函数模板:
function [segments, timeIdx] = batchWindowing(data, fs, winLen, overlap) %BATCHWINDOWING 将一维信号分割为重叠加窗段。 % 语法: % [segments, timeIdx] = batchWindowing(data, fs, winLen, overlap) % % 输入: % data - 一维信号,双精度列向量 % fs - 采样率,Hz % winLen - 窗长,秒 % overlap - 重叠率,0~1,默认0.5 % % 输出: % segments - 窗段矩阵,每列为一个窗段 % timeIdx - 时间索引向量,单位为秒 % % 示例: % [segments, t] = batchWindowing(x, 1000, 0.5, 0.5); % % 参见: % BUFFER, CUMSUM % ---------- 输入检查 ---------- if overlap < 0 || overlap >= 1 error('重叠率必须位于[0, 1)区间'); end % ---------- 计算窗参数 ---------- winLenSamples = round(winLen * fs); hopSamples = round(winLenSamples * (1 - overlap)); % ---------- 分窗 ---------- segments = zeros(winLenSamples, floor((length(data)-winLenSamples)/hopSamples)+1); idx = 1; for startIdx = 1:hopSamples:(length(data)-winLenSamples+1) segments(:, idx) = data(startIdx:startIdx+winLenSamples-1); idx = idx + 1; end % ---------- 时间索引 ---------- timeIdx = (0:size(segments,2)-1) * hopSamples / fs;这个模板的核心逻辑是:帮助文本回答“别人怎么调用这个函数”,分节注释回答“这段代码在干什么、为什么这么做”。输入检查放在最前面,避免别人传错参数后看不懂报错。
4.3 利用编辑器预设项自动加载自定义模板
Matlab编辑器支持自定义新函数模板。打开“预设项”菜单,在“编辑器/代码生成”里可以看到“函数模板”配置,把上面那段套进去,以后在编辑器里新建函数文件时会自动带上注释框架。这样写模板就不是贴在文档里,而是真正做到“打开编辑器就能看到”。
对于脚本类型的文件,我习惯的做法是维护一个template_script.m,每次新建脚本就复制一份,把文件头替换掉。这比在预设项里配置更快,因为脚本模板通常会包含clear、close all、clc等固定开头,还带着常用绘图参数的注释。
4.4 脚本、函数和类,注释策略不同
Matlab里三样东西:脚本(script)、函数(function)、类(classdef),注释重点完全不一样。脚本是一个“流程”,注释应该写清楚步骤和每一步的目标,适合用%%分块配合发布功能。函数是“接口”,注释重点是输入输出、调用方式、算法依据。类则要兼顾属性和方法的说明,属性的注释尤其不能省,不然下一任维护者根本不知道某个字段的含义。
举一个例子,类里定义属性:
properties % 传感器ID,对应采集设备配置文件中的编号 sensorID (1,1) string {mustBeNonzeroLength} = "S001"; % 采样率,固定为1000Hz sampleRate (1,1) double {mustBePositive} = 1000; end属性注释拉长写,比方法注释更优先。因为属性是类的“状态”,一旦理解错了,整个逻辑都会判错。
5. 让注释质量可检查:checkcode 与代码分析器的实用姿势
5.1 用代码分析器提示“缺注释”
Matlab自带代码分析器(Code Analyzer)会实时在编辑器中显示橙色警告,其中一类和注释相关。最常见的包括:函数缺少帮助文本、未定义变量、可疑的语法用法。虽然它不会强求你写注释,但如果你写了帮助文本,分析器对函数完整性的检查会更友好。
在命令行手动运行checkcode,可以拿到一份分析报告:
checkcode('batchWindowing.m')它会输出行号、严重程度和问题描述。配合注释规范,可以把报告中“注释相关”的提示作为质量标准之一。比如“在函数首行前未找到帮助文本”这类警告,我要求自己的代码中不能出现。
还有一种用法是标记“有意为之”的警告,在代码行尾写%#ok<*SAGROW>这种抑制标记。特别是在循环里逐步扩展数组时,Matlab会提示预分配以提升性能,但如果你知道数据量不大,就可以加%#ok<SAGROW>并注释说明原因。这样既保留警告的提示作用,又不会被它噪声打扰。
5.2 简易注释比例检查脚本,团队落地必备
除了编辑器自带检查,我还写过一个小工具来做“注释量”检查。思路很简单:读取所有.m文件,统计纯注释行(以%开头且非%%)、整行空白、代码行,算出一个注释密度。这个数字虽然不代表注释质量,但能快速发现哪些文件“完全没有注释”。
下面的代码是我常用的检查脚本,很短,但效果很好:
% 统计当前目录所有.m文件的注释行占比 files = dir('*.m'); for k = 1:length(files) content = fileread(files(k).name); lines = strsplit(content, '\n'); totalLines = length(lines); commentLines = 0; for i = 1:length(lines) trimmed = strtrim(lines{i}); if startsWith(trimmed, '%') commentLines = commentLines + 1; end end fprintf('%s: %d/%d = %.1f%%\n', ... files(k).name, commentLines, totalLines, ... 100*commentLines/totalLines); end我在团队里推荐的合理区间是:算法类文件注释行占比20%~35%;简单的工具类脚本可以低于10%;如果超过50%,通常说明注释写得太啰嗦,该精简了。用这个脚本定时跑一遍,能很快发现“重点文件注释不够”的情况。
5.3 把注释纳入交付检查:团队执行建议
在实际项目落地时,我见过最失败的执行方式是压一个“注释率必须超过30%”的硬指标。这个指标很可能导致人人都在代码里堆积废话来凑数。更合理的做法是三层检查:
- 新函数必须提供帮助文本。这个可以在人审时看,也可以让
checkcode提示。 - 涉及公式和算法的代码段,至少写一行“来源/原理”注释。这一条靠抽查。
- 文件头必须保留修改记录。这个可以配合Git提交信息一起看。
用一种“宁可少但要准”的态度去要求,比“必须写满”更有效。我在某项目里就靠这三条规则,把几个核心文件的注释质量提了上来,而整体注释量反而下降了。
6. 案例与避坑:从真实代码里看注释怎么补
6.1 一个重构前后对比的例子
为了说清楚什么才是有效注释,我用一段实际改写经历做对比。这是某个模拟项目里的频谱分析片段,原代码注释风格是那种“复读机式”的。
改前:
% 对信号做FFT Y = fft(x); % 取幅值 P = abs(Y/N); % 只取一半 P = P(1:N/2+1); % 设置频率轴 f = fs*(0:(N/2))/N; % 画图 plot(f, P);这段注释没有错误,但也基本没用。它只是把代码翻译了一遍,读完仍然不知道为什么要这么做、N是什么、P为什么取一半。
改后:
%% 幅值谱计算 N = length(x); % 单边频谱:实信号的FFT结果关于奈奎斯特频率对称,只取前半段 Y = fft(x); P = abs(Y/N); P = P(1:N/2+1); P(2:end-1) = 2*P(2:end-1); % 恢复单边总幅值,去掉直流和奈奎斯特分量要特殊处理 f = fs*(0:(N/2))/N; % 频率轴,单位Hz figure; plot(f, P); xlabel('频率 (Hz)'); ylabel('幅值');改后的注释多了两句“为什么取一半”和“为什么乘2”,还标出了直流与奈奎斯特分量的特殊处理。这个改动看似很小,但下一任接手的人不会再踩“幅值比预期小一半”的坑。
6.2 常见“注释坏味道”与修改建议
我总结了几个在Matlab代码里反复出现的注释坏味道,基本可以覆盖大多数人的问题:
| 现象 | 典型表现 | 修改建议 |
|---|---|---|
| 复读机式注释 | % 将i加1 | 删除,只保留代码看不懂的信息 |
| 注释掉的旧代码 | 大段代码被%注释挂在文件里 | 用Git保留历史,平时不留尸体 |
| 过期注释 | 变量名改了注释没改 | 改代码时同步检查相邻注释 |
| 谜语缩写 | % calc. pint. | 重写为完整短句 |
| 注释位置错乱 | 注释挂在下一段代码上 | 让注释贴近它所解释的那行 |
| 参数单位缺失 | % cutoff = 50 | 明确单位,如% cutoff = 50 Hz |
其中“注释掉的旧代码”是我见过最顽固的问题。很多人担心删掉后找不到,其实项目用Git的话历史里一查就有,没必要留下一大段“死注释”干扰阅读。
6.3 我关于自定义注释规范的三条实操经验
第一,模板一定要嵌入编辑器,而不是写在团队文档里。只要每次新建文件都要“打开文档去复制模板”,这个规范就很难坚持。把模板配置到预设项或做一个标准模板文件,直接复制,才是可持续的。
第二,写注释时先写“为什么”,再写“是什么”。代码本身已经回答了“做什么”,注释就应该回答“为什么这么做”。遇到参数和公式,直接写单位和出处,不要用口头缩写。
第三,不必追求每条注释都完美,但文件头的修改记录、函数的帮助文本、算法段落的来源说明这三个位置,是绝对不能省的。这三处覆盖了90%的“未来维护时最难猜的信息”。
回到开头那个卡尔曼滤波脚本的教训。现在我的每个文件顶部都有标准的说明块,函数都有帮助文本,算法段落都有公式来源。每次打开一个自己三个月前写的脚本,我都能在两分钟内搞清楚它要干什么、为什么这么干、改哪里能达到目的。这种感觉,比每次重新“考古”舒服太多了。如果你也经常被自己写的代码绕晕,不如从今天起,先把一套自定义注释模板配好,这个投入的回报率,是你所有代码改动里最高的。