MATLAB编程风格指南(第二版)
MATLAB
编程风格指南
——MATLAB Style Guidelines
第二版,2014年3月
作者:
Richard Johnson
, 译者:
DK=navoski
“You got to know the rules before you can break 'em. Otherwise it's no fun.”
――Sonny Crockett in Miami Vice
目录
MATLAB 编程风格指南
一、概述
二、命名规则 1、变量2、常量3、结构体4、函数5、通则
三、基本语句 1、变量与常量2、全局变量3、循环语句4、条件语句5、通则
四、排版、注释与文档 1、排版2、空格3、注释
五、文件及组织 1、M 文件2、输入与输出3、工具箱
六、参考文献
一、概述
关于编写
MATLAB
代码的建议通常强调效率,例如“不要用循环”。本指南则不同,它关注的是正确性、清晰度与通用性,目的是帮助人们写出更可能正确、可理解、可共享、可维护的代码。
有些编码方法更好,就是这么简单。编码约定使错误变得明显。正如
Brian Kernighan
所写的,“写得好的程序比写得差的程序要好——它们错误更少,更容易调试和修改——所以从一开始就考虑风格是很重要的。”
当人们看你的代码时,他们能明白你在做什么吗?本指南的核心可以简单地表达为“避免‘只写’的代码”。
本指南列出的
MATLAB
代码编写建议与软件开发社区的最佳实践一致,通常与
C
、
C++
与
Java
的风格指南相同,只是针对
MATLAB
的特性与历史进行了修正。文中的建议是基于多种其他编程语言的风格指南与个人经验而来。本指南虽然针对
MATLAB
而写,对于相近的语言,如
Octave
、
Scilab
和
O-Matrix
等的编程也有所帮助。
随着
MATLAB
语言的发展和越来越广泛的使用,风格问题变得越来越重要。在早期版本中,所有变量都是双精度矩阵;现在有许多数据类型可用。
MATLAB
使用已经从小规模的原型代码发展到由团队开发的大型复杂的生产代码。与
Java
集成是标准的,
Java
类可以出现在
MATLAB
代码中。所有这些变化都使得清晰的代码编写变得更加重要和更具挑战性。
指南不是戒律。它们的目标只是帮助人们写出好代码。许多组织有理由偏离其中的一些指导方针,但是大多数组织将受益于采用一些风格指导方针。
二、命名规则
Pathrick Raume
:
“A rose by any other name confuses the issue。”
(玫瑰如果不叫玫瑰,问题就会很复杂)
命名规则的目的是为了帮助读者和程序员。为开发团队建立命名规则是非常重要的,这个过程可能会产生很大的争议。没有一个命名规则会让每个人都满意。
遵守规则比规则的细节更重要。本节描述一种常用的规则,许多
MATLAB
和其他语言的程序员都会熟悉这种规则。
1、变量
变量名应记录它们的含义或用途。
(1)变量名以小写字母开头、大小写混合形式命名。
这是其他编程语言的通用做法。以大写字母开头的命名在其他语言中通常保留为类或结构体。
linearity
,
credibleThreat
,
qualityofLife
如果很短的变量名的惯例用法是大写的,并且不太可能成为复合变量名的一部分,那么很短的变量名可以是大写的。如典型的领域专用名,
E
代表杨氏模量,而
e
可能会引起误解。
有些程序员喜欢用下划线来分隔复合变量名的各个部分。这种技术虽然可读,但在其他语言中并不常用。另外的考量是在图的标题、标签和图例的变量名中使用下划线,
MATLAB
的
Tex
解释器将把下划线作为下标开关读取,因此你需要为每个文本字符串使用参数/值对
'Interpreter'
,
'none'
。
(2)作用域大的变量应该使用有意义的变量名,作用域小的变量可以使用短的变量名。
在实践中,大多数变量都应该使用有意义的命名。在某些情况下应保留使用简短的名称,以澄清语句的结构或与预期的一般性相一致。例如,在一般的函数中,使用诸如
x
,
y
,
z
,
t
这样的变量名可能是合适的。用于临时存储或索引的临时变量可以简短。读取这些变量的程序员应该能够假定它的值在几行代码之外没有被使用。临时变量是整数类型时的常见命名是
k
、
m
、
n
,是双精度浮点数时的常见命名是
s
、
t
、
x
、
y
和
z
。处理复数的程序员可以选择保留
i
或
j
,或者两者都保留为
- 1
的平方根。然而,
MathWorks
推荐使用
1i
或
1j
作为虚数单位,这样复数算数计算速度更快,可靠性更好。
(3)变量表示对象数量时,使用前缀
n
。
这种命名方法取自数学,是用来表示对象数量的既定惯例。
nFiles
,
nSegments
一个
MATLAB
特定选择是用m来表明行数(基于矩阵标记法),例如变量名:
mRows
。
(4)遵循关于众数
(pluralization)
变量命名的惯例。
两个变量名只差一个结尾字母
s
的情况应该避免。一些程序员将变量命名为单数或众数,但其他人发现这并不合适。
一个可接受的众数变量命名法是使用例如"
Array
"这样的后缀。
point
,
pointArray
,
pointList
一个可接受的单数变量命名法是使用前缀,例如:
thisPoint
大多数程序员不使用"
the
"作为单数变量的前缀。
(5)代表单个实体数字的变量命名可以加后缀
No
或
Num
,或者加前缀
i
。
符号
No
来自数学,是声明实体数字的惯例。例如:
tableNo
,
empolyeeNo
(6)循环变量应以
i
、
j
、
k
等为前缀。
前缀i有效地使变量成为一个被命名的循环变量。
iTable
,
iEmployee
这种标记法来自数学,是声明循环变量的惯例。
for iFile = 1 :nFiles
end
一些程序员为了方便使用单字母变量名
i
,
j
作为循环变量。使用显式复数的程序员讨厌这种做法。
对于嵌套循环,循环变量通常应该按字母顺序排列。一些面向数学的程序员使用以i开头的变量名表示行,以j开头的变量名表示列。这很有帮助。
for iFile = 1 :nFiles
for jPosition = 1 :nPositions
end
(7)避免否定式的布尔变量命名。
当与逻辑运算取非的操作符号相连时,采用否定式的布尔变量命名不好理解,容易产生双重否定的错误理解。
~isNotFound
没有采用
isFound
直观。
应该使用
isFound
和
~isFound
,避免使用类似
isNotFound
这样的变量名。
(8)缩写形式,即使缩写形式通常是大写的命名,也应该小写或混合使用 。
全部使用大写字母的变量名和上面给出的命名规则相冲突。按照命名规则,这类变量名应写为类似
dVD
,
hTML
等形式的变量名,但很显然这不具有可读性。当这样的变量名与其他的单词相连时,其可读性严重降低。建议采用全小写,或按照命名规则在变量名中间的单词首字母大写,其余小写。
采用:
html
,
isUsaSpecific
,
checkTiffFromat()
避免使用:
hTML
,
isUSASpecific
,
checkTIFFFormat()
(9)不能使用关键字或者特殊值的名称作为变量名。
当
MATLAB
的关键词保留字或者内建的特殊值被重新定义的时候,
MATLAB
会给出一个模糊的出错信息或奇怪的结果。保留字通过
iskeyword
命令列出,特殊值在文档中列出。
特殊值 | 意义 | 特殊值 | 意义 |
---|
(10)使用特定领域的惯用名称。
如果软件使用者是某个领域或用户组,应使用与领域惯例一致的名称。
(11)避免变量名与函数名相似。
MATLAB
中有几个函数名很容易被用作变量名。在脚本中这种用法会遮盖函数,并导致错误。在一个函数中,如果变量名与
MATLAB
的函数名相同,可能会导致错误。
一些标准的函数名作为变量名出现在代码示例中,如
alpha
,
angle
,
axes
,
axis
,
balance
,
beta
,
contrast
,
gamma
,
image
,
info
,
input
,
length
,
line
,
mode
,
power
,
rank
,
run
,
start
,
text
,
type
使用大家熟知的函数名作为变量名也会降低可读性。如果你想在变量名中使用标准函数名,比如
length
,那么你可以加一个限定符,比如单位后缀,名词或形容词前缀。
lengthCm
,
armLength
,
thisLength
(12) 避免匈牙利标记法。
匈牙利标记法是编程中变量的一种命名习惯。变量名以标明该变量数据类型的小写字母开始。
至少有两种匈牙利标记法被一些软件开发人员使用。匈牙利标记法命名的变量名通常包含一个或两个前缀、一个基础名和一个限定词后缀。这些名称可能非常难看,特别是当它们是缩写字符串时。如果像匈牙利标记法建议的那样,前缀标识变量的数据类型,如果数据类型改变,则变量名的所有关联都需要更改。
使用
thetaDegrees
,避免
unit8ThetaDegrees
。
2、常量
MATLAB
没有真正的常量(除了对象中的常数属性)。使用标准做法来命名和定义常量,以便能够识别它们,而不是无意中重新定义它们。
(1)*.m文件中局部作用域的常量名应该都是大写的,使用下划线来分隔单词。
这是其他语言的常用做法。
MAX_ITERATIONS
,
COLOR_RED
使用有意义的常量名。
使用
MAX_ITERATIONS
,避免
TEN
,
MAXIT
。
(2)由同名函数输出的常量的命名应该全部为小写或大小写混合。
MathWorks
使用了这种方法。例如,常量π实际上是一个函数。
offset
,
standardValue
(3)常量命名可以加通用类型名作为前缀。
这样的命名给出了哪些常量同属一类和表示什么概念的附加信息。
COLOR_RED
,
COLOR_GREEN
,
COLOR_BLUE
3、结构体
(1)结构体的命名应该以一个大写字母开头。
这种做法与其他语言一致,有助于区分结构体与普通变量。
(2)结构体的命名是隐式的,不需要再字段名中出现。
重复是多余的。
应采用
Segment.length
避免用
Segment.SegmentLength
(3)注意结构体的字段名。
当设置结构体中字段的值时,如果该字段已经存在,
MATLAB
将它的值替换为新值;如果不存在,则在结构体中创建一个新字段。当一个结构体有字段
Acme.source = ‘CNN’;
你打算更新它的值,输入
Acme.sourceName = ‘Bloomberg’;
结构体将有两个字段。
4、函数
函数命名应该记录他们的用途。函数名与它的m文件名保持相同是明智的做法。
(1)函数名应该采用小写字母或大小写混合。
最初所有的
MATLAB
函数名都是小写的。
linspace
,
meshgrid
MathWorks
提供的几乎所有函数仍然遵循这个规则。对于较长的复合名称,这种做法不合适,如短命的
isequalwithequalnans
。
在其他语言中,函数名通常以小写开始,使用混合大小写。许多
MATLAB
程序员都遵循这一规则。
predictSeaLevel
,
publishHelpPages
有些程序员喜欢在函数名中使用下划线,但这种做法并不常见。
(2)使用有意义的函数名。
MATLAB
存在一种不好的传统做法,那就是采用短的、有些模糊的函数名。这或许是因为老DOS命名规则的8个字母的限制造成的。这个问题已经不重要了,为了提高可读性,通常应该避免使用这种传统做法。例如:
采用:
computetotalwidth()
避免:
compwid()
但对于那些在数学中广泛使用的缩写或者首字母缩写的情况是个例外。譬如:
max
,
gcd
名字短的函数应在函数声明后的第一行注释编写清晰的描述。
(3)根据输出值命名只有一个输出的函数。
这是
MathWorks
代码中的惯例,譬如:
mean
,
standarderror
(4)没有输出或只返回句柄的函数应该根据它们所做的事情来命名。
这种做法增加了可读性,明确了函数应该做什么(或不应该做什么)。这使得代码容易避免意想不到的副作用。例如:
plot
(5)为访问对象或属性的函数保留前缀
get
/
set
。
这是
MATLAB
和其他语言的惯用做法。一个合理的例外是set用于逻辑集合操作。
getobj
,
setappdata
(6)为有计算内容的函数保留前缀
compute
。
坚持使用这个前缀可以提高代码的可读性。给读者一个直接的线索,这个函数能做什么。
computeWeightedAverage
,
computeSpread
避免可能令人困惑的替代方案,如
find
或
make
。
(7)为查找内容的函数保留前缀
find
。
给读者一个直接的线索,这是一个设计最少计算量的简单查找方法。坚持使用这个前缀可以提高代码的可读性,是被过度使用的前缀
get
的好的替代。
findOldestRecord
,
findTallestMan
(8)在建立对象或变量时,考虑使用前缀
initialize
。
美式英语的
initialize
比英式英语的
initialise
更受欢迎。避免使用缩写
init
。
initializeProblemState
(9)布尔函数的命名使用前缀
is
。
这在
MATLAB
和其他语言的常见做法。
isOverpriced
,
isComplete
除了
is
前缀外,某些情况下还有一些更好的替代前缀,例如
has
、
can
和
should
。
hasLicense
,
canEvaluate
,
shouldSort
(10)为互补操作保留互补名称。
通过对称降低复杂性。
get/set
,
add/remove
,
create/destroy
,
start/stop
,
insert/delete
,
increment/decrement
,
old/new
,
begin/end
,
first/last
,
up/down
,
min/max
,
next/previous
,
open/close
,
show/hide
,
suspend/resume
等
(11)避免无意识地遮盖。
一般来说,函数名应该是唯一的。遮盖(两个或者多个函数具有相同的函数名)会增加不可预测的行为或者错误。
which –all
或者是
exist
命令可以检查是否有函数名重复。
重载函数当然有相同的名称。当多态函数足够时,不要创建重载的情况。
为提高代码性能,
MATLAB
建议“避免重载内置函数,避免对任何标准
MATLAB
数据类重载内置函数。”
5、通则
(1)考虑为有量纲的变量和常量加上单位后缀。
在一个项目中使用单一的单位集是一个很有吸引力的想法,但是很少完全实现。添加单位后缀有助于避免几乎必然的无意识的混合单位表达式。
示例:
incidentAngleRadians
(2)避免使用缩写来命名。
利用完整的单词命名可以减少歧义,有利于使代码自成为文档
(self-documenting)
。
采用:
computeArrivalTime
避免:
comparr
特殊领域的常用语的简写或者首字母缩写形式更容易被理解,因此应该保持缩写形式。但即使是这些词,人们也可能从它们第一次出现时的定义注释中受益。
示例:
html
,
cpu
,
cm
(3)考虑使命名可发音。
可发音的命名易于读和记忆。
(4)所有的命名都应该用英语写出。
MATLAB
是以英语发布的,英语是国际开发的首选语言。
三、基本语句
1、变量与常量
(1)变量名不应该被重复使用(赋予为不同意义),除非因为内存限制的需要。
通过确保所有的概念都被唯一地表示,以此增强可读性。减少定义误解造成的错误。
(2)注意在文件开头的注释中编写重要变量的说明。
在其他的编程语言中,在声明变量的地方添加变量注释是一种标准操作。
MATLAB
不需要声明变量,对变量的说明可以写在文件开头的注释中,也可以写在变量出现处的注释中。
% pointArray Points are in rows.
THRESHOLD = 10; % Maximum noise level found.
2、全局变量
(1)尽量少使用全局常量。
使用
m
文件或
mat
文件定义全局常量。这样的话,常量在什么地方定义就很清楚,避免无意识地重新定义。如果m文件的访问开销造成执行速度问题,可以考虑使用函数句柄。
(2)尽量少使用全局变量。
函数的清晰性和可维护性得益于显式参数传递,而不是使用全局变量。全局变量的某些使用可以用
persisitent
(定义持久变量)和
getappdata
(检索应用程序定义的数据)替代。另一种策略是用函数替换全局变量。
3、循环语句
(1)在循环之前初始化循环结果变量。
初始化循环结果变量可以提高循环执行速度,避免如果循环没有对所有可能的索引执行时使用虚假值。这种初始化有时被称为预分配。将结果变量初始化放置在循环之前,可以更容易地看到变量被初始化。这种做法也使得将所有相关代码复制到其他地方使用变得更容易。
refult = nan(nEntries,1);
for index = 1 :nEntries
result(index)= foo(index);
end
在初始化语句和
for
行中都使用命名的变量作为参数。
(2)在循环中应该尽量少使用
break
与
continue
。
这个关键字通常是不必要的,只有当它被证明比结构化选项具有更高的可读性时才应该使用。
(3)嵌套式循环的
end
行要有标识注释。
在长嵌套循环的
end
行添加注释可以帮助澄清哪些语句在哪个循环中,以及在这些地方执行了什么任务。
4、条件语句
(1)避免复杂的条件表达式,引入临时逻辑变量替代。
通过将逻辑变量指定给表达式,程序得到自动文档化。这样的结构更易于阅读和调试。
if (value>=lowerLimit) & (value<=upperLimit) & ~ …
ismember(value, … valueArray)
end
应替换为:
isValid = (value >= lowerLimit) &…
(value <= upperLimit);
isNew = ~ismember(value, valueArray);
if (isValid & isNew)
end
(2)在
if else
语句中,将通常情况放在
if
部分,例外情况放在
else
部分。
这种做法通过防止特殊情况掩盖执行的正常路径来提高可读性。
fid = fopen(fileName);
if(fid ~= -1)
end
(3)避免使用
if 0
条件表达式。
if 0
用于跳过某段程序。
确保这种用法不会掩盖执行的正常路径。若要暂时绕过代码执行,请使用编辑器的块注释特性来代替此表达式。
(4)
switch
语句应该包含
otherwise
条件。
遗漏
otherwise
条件是常见错误,这或许会导致不可预测结果。
switch (condition)
case ABC
statements;
case DEF
statements;
otherwise
statements;
end
(5)当
condition
能清楚地写为表达式时用
if
,当
condition
能清楚地写为变量时用
switch
。
if
和
switch
的用法可能有重叠。遵循这个指导原则有助于提供一致性。
通常,
switch
语句只应在基于枚举类型(如整数、字符或字符串)选择其中一种情况的时候。基于实数选择其中一种情况时,不应使用
switch
,因为实数很少相等,所以匹配的情况可能不会发生。
switch
变量通常应该是一个字符串。字符串在这种情况下工作得很好,它们通常比枚举情况更有意义。
5、通则
(1)避免含糊的代码。
或许是受莎士比亚“言以简为贵”的影响,一些程序员存在这样一种倾向:将
MATLAB
代码写得很简洁,甚至含糊。编写简洁的代码是一种探索语言特色的方式。然而,在很多情况下,清晰才是目的。正如
MathWorks
的
Steve Lord
所写:“一个月后,如果我看这段代码,我能明白它是干什么的吗?”
(2)使用括号。
MATLAB
文档中解释了运算符优先级规则,但是谁想记住这些细节呢?如果有任何疑问,请使用括号来理清表达式。括号对扩展的逻辑表达式特别有帮助。
(3)尽量少在表达式中使用数字。
可能会发生变化的数字通常应该命名为常量。如果数字本身没有明显的含义,则通过引入命名的常量来增强可读性。
更改常量的定义要比在文件中查找和更改所有与字面数字相关的值容易得多。
(4)在小数点前写一个数字。
这符合数学的语法约定。另外,
0.5
比
.5
更具可读性;它不太可能被读取为整数5。
采用
THRESHOLD = 0.5;
避免使用
THRESHOLD = .5;
(5)要小心浮点数的比较。
二进制表示法可能导致麻烦,如下所示:
shortSide = 3;
longSide = 5;
otherSide = 4;
longSide^2 == (shortSide^2 + otherSide^2)
ans =
1
但是
scaleFactor = 0.01;
(scaleFactor*longSide)^2 ==
((scaleFactor*shortSide)^2 + …
(scaleFactor*otherSide)^2)
ans =
0
更好的方法是测试这些值之间的差异是否足够小。
(6)对逻辑表达式使用自然、直接的形式。
逻辑表达式包括否定可能很难理解,尽量使用肯定表达方式。
使用
iSample >= maxSamples;
避免使用
~(iSample < maxSamples);
(7) 做好错误准备。
一般来说,错误应该在低级别代码中捕获,并在更高级别代码中修复或传递。防止错误条件的有帮助的保护工具是包含
MException
类的
try-catch
结构。另一种保护方法是在
if
语句中使用正确有序的表达式,短路求值(
evaluation short circuiting
,求布尔表达式的时候程序并不会执行全部的
Expression
)可以避免计算会触发错误的表达式。
try
statements
catch exception
statements
end
try statements, catch statements end
执行
try
块中的语句并在
catch
块中捕获产生的错误。此方法允许你改写一组程序语句的默认错误行为。如果
try
块中的任何语句生成错误,程序控制将立即转至包含错误处理语句的
catch
块。
exception
是
MException
对象,你可以用它来标识错误。
catch
块将当前异常对象分配给
exception
中的变量。
(8)在用于获取输入的函数中包含有效性检查。
无效的输入通常会导致程序错误并停止执行。有效性检查给予更优雅的错误处理。有用的工具包括
validateattributes
和
inputParser
。
(9)尽可能避免使用
eval
。
eval(expression)
计算
expression
中的
MATLAB
代码。
M-Lint
代码检查报告显示潜在的错误和问题,也就提供了改进M文件的可能。
与替代方法相比,包含
eval
的语句往往更难正确编写,更难读取,执行更慢。使用
eval
不支持
M-Lint
的彻底检查。使用
eval
的语句通常可以通过将命令改为函数,或通过
setfield
和
getfield
使用结构的动态字段引用来改进。
(10)尽可能将代码写成函数形式。
函数通过使用不在工作区的内部变量进行模块化计算。这使输入和输出变量更加明显,并且比脚本更干净、更灵活、设计得更好。脚本主要在开发中使用,因为它们提供了变量维度、类型和值的直接可见性。
(11)编写自动化代码。
keyboard
- 将控制权给予键盘;
input
- 请求用户输入;
尽量减少使用
keyboard
和
input
,以支持自动化执行和测试。
四、排版、注释与文档
1、排版
排版的目的是帮助读者理解代码,缩进对揭示结构特别有用。
(1)将代码内容控制在前
80
列之内。
80
列是编辑器、终端模拟器、打印机和调试器的常见尺寸。几人之间分享的文件应遵守这些约束。程序员之间传递文件的时候,避免无意的换行,可以增强代码的可读性。
(2)在长行恰当的地方进行切分。
当语句长度超过
80
列限制的时候应该分行。
通常:
● 在逗号或空格后分行;
● 在操作符之后分行;
编辑器在换行符(
…
)之后提供缩进。可以选择包含额外的空格,以使新行与前一行表达式的开头对齐。
totalSum = a +b +c +…
d+e;
function (param1,param2,…
param3)
setText(['Long line split'…
'into two parts.']);
(3)基本缩排应该是3或者4个空格。
好的缩排或许是展示程序结构唯一的好方法。
1
个空格缩进太小,不能展示代码的逻辑布局。有时建议缩进
2
个空格,以减少嵌套语句中保持在
80
列内所需的换行次数,但
MATLAB
通常不会深度嵌套。缩进大于
4
个空格会使嵌套代码难以阅读,因为它增加了分行的可能性。缩进
4
个空格是
MATLAB
编辑器中当前的默认值。
(4)缩进与
MATLAB
编辑器一致。
MATLAB
编辑器提供了缩进,以展示代码结构,并与
C++
和
Java
的推荐实践一致。如果使用不同的编辑器,请尽量保持一致。
(5)每行代码只写一条可执行语句。
这种做法提高了可读性,并可以加快执行速度。
(6)短的单个
if
,
for
或者
while
语句可以写在一行。
这种实践更紧凑,但它的缺点是没有缩进格式提示。
if(condition), statement; end