从来没有文档太多这回事。清晰常常意味着重复。将您的代码看成是向世界展示的东西。世界上有许多人。您认为冗余的注释可能对某人大有用处。五年之后,当您添加新特性时,它甚至可能对 您
大有用处。
基本注释
编写程序时,使用良好的规划。不必事先确定每个细节,但是应该将程序分成几个组成部分,并且使用注释来填充间隙。
下列是我个人的编码风格。您可能不喜欢它,但是请客观地看待它,并且看看有什么可以为您及您的团队所用。
首先,考虑一下该注释的预期读者。尽量使注释足够清晰,以便第三方顾问领会。代码越复杂,就应该添加越多的注释以阐明目的。不要将注释放在以后再做;让它们成为您思考过程的一部分:问题、解决方案、注释,然后调试。在调试之前创建注释尤为重要。您自己代码中的注释将有助于更好和更快地调试。
有时不仅陈述问题的解决方案很有帮助,而且陈述问题本身也是很有帮助的。例如:
清单 1
# function: do_hosts
#
# purpose: to process every host in the /etc/hosts table and see if it
# resolves to a valid IP
#
# solution: read the list of hosts as keys in a hash, then go through
# the list of keys (hosts) and store the IP address for each host as
# the value for that key, or undef() if it doesn't resolve properly.
# Return a reference to the hash, or undef if the /etc/hosts file was
# not accessible.
推荐简化为:
清单 2
# function: do_hosts: process every host in the /etc/hosts table and
see if it
# resolves to a valid IP; return a reference to the hash (key=host,
# value=IP or undef), or undef if the /etc/hosts file was not
accessible.
还有另一种方法:
清单 3
# do_hosts: returns a ref to hash of hosts (key=host, value=IP/undef)
# from /etc/hosts
以上方法都有效,这取决于 do_hosts() 的复杂程度。如果函数只有两行,则不要浪费时间来写三段注释。但是,如果函数有几页,则不要吝惜说明。
注释程序的开头
程序应该以对其目的简要说明来开头。不要让人们滚动几页才能弄清您在做什么。如果正在使用版本控制系统(例如,CVS),则将适当的头(例如,ID 头)放在文件的开头。尽量简练。两行,最多四行,应该足以对程序进行简要描述。给出联系人姓名、电子邮件、电话号码或团队联系方式。
清单 4
#!/usr/bin/perl -w
# whodunit.pl: A script to solve a murder mystery
# by joe@shmoe.com $Id: whodunit.pl,v 1.92 2000/08/08 19:08:50 joe Exp
$
第一行上的注释是大多数 UNIX 系统上的一种标准方法,用来表示执行脚本时运行哪一个应用程序(在“!”后的所有东西被认为是解释器名称)。-w 标志表示打开警告标志 — 总是一个很好的主意,即使对于很有经验的程序员来说。
第二行(第一个注释行)是程序及其目的的简短描述。第三行(第二个注释行)给出了作者姓名和唯一标识文件的发布日期和版本的 ID 头。RCS 和 CVS 特别地使用 ID 头,它在提交脚本时自动更新。有关 RCS 和 CVS 的更多信息,请参阅本文后面的 参考资料。
注释初始化节
初始化节应该在逻辑上和物理上与程序的开头分开,例如,可以通过额外注释或让它处于文件的开头来实现。初始化节,与上面描述的程序的开头相反,包含程序启动时执行的实际代码。在 Perl 中,初始化节应该由下列部分组成(最好按该顺序):
- 模块和编译指示
- 常量
- BEGIN/END/INIT/CHECK 子例程
- 初始化代码
模块和编译指示
Perl 中的 use 关键字命令解释器装入模块或者打开编译指示(“no pragma”关闭编译指示)。编译指示将解释器引入正确的方向。例如, use utf8 告诉解释器要准备好 UTF-8 编码的数据文件和流。
使每个模块的注释在水平方向上排列整齐,并且每个模块或编译指示有一个注释,是大有好处的。
清单 5
use Data::Dumper; # for debugging printouts
use strict; # be strict - pragma for the
interpreter
use POSIX; # use the POSIX functions
第一次这样做时,只需要复制和粘贴就可以将模块和编译指示放入新程序。我建议“strict”编译指示。此外,它还将确保您诚实地声明变量,以我的经验,这是 Perl 中的错误源,就象内存分配是 C/C++ 中的错误源一样。
请使用 perldoc 命令来察看所有模块和编译指示文档。例如, perldoc strict 说明了有关 strict 编译指示的所有信息 — 它可以做什么,如何使用,等。
某些编辑器具有总是将注释放在特定位置的良好能力(在 Emacs 中,indent-for-comment 命令自动完成该操作)。您自己应该彻底熟悉编辑器的命令。这值得花时间。
常量
虽然可以作为另一个 Perl 编译指示来查看常量,但是它们应该有自己的节。它们的注释应该类似于模块和编译指示的注释,但是,如果将箭头对齐,将更好看:
清单 6
use constant ALPHA => 1; # alpha code
use constant BETA => 2; # beta code
use constant GAMMA => 3; # gamma code
use constant USER => 4; # user ID offset
use constant GROUP => 5; # group ID offset
use constant DEPT => 6; # dept. ID offset
BEGIN/END/INIT/CHECK 子例程
象注释常规子例程一样注释 BEGIN/END/INIT/CHECK 子例程(有关更多信息,请参阅 perldoc perlmod )。在文件的任何位置都可以创建它们,并且有可能多次定义它们。我建议将它们放在文件的开头或结尾,这样易于找到它们。请注意只有一行的 BEGIN 函数不需要详尽的注释。
清单 7
# BEGIN: executed at startup, assigns 'root' to the USER environment
variable
BEGIN
{
$ENV{USER} = 'root';
}
初始化代码
实际代码出现在初始化节的最后。同样,如果可能,在单个块中将注释对齐。
清单 8
$| = 1;
# auto-flush the output
$Data::Dumper::Terse = 1; # produce human-readable
Data::Dumper output
# define the configuration variables
my $config = AppConfig->new();
$config->define(
# list of undo commands
'UNDO' => { ARGCOUNT => ARGCOUNT_LIST },
# file to log data
'LOG_FILE' => { ARGCOUNT => ARGCOUNT_ONE },
);
$config->file(whodunit.conf'); # load the whodunit
configuration file
初始化代码打开自动刷新(auto-flushing)显示(因此,将立即显示输出),然后告诉 Data::Dumper 模块产生可读的输出,最后创建 AppConfig 配置。
注释常规代码
注释常规代码很容易。如果可能,只要将注释对齐,尽量简练,当事情不清楚时,不要害怕深入解释它们。
清单 9
print Dumper \%ENV; # print the full ENV hash
# get the environment variable names that begin with USER
@user_vars = grep(/^USER/, keys %ENV);
# print the values in all the variables that begin with USER, using a
# hash slice
print Dumper @ENV{@user_vars};
print "Done\n"; # print "done" message
# TODO: find better method of sorting variables
# TODO: use Data::Dumper with variable names
请注意,注释从第 0 列或第 40 列开始。一致性使注释更具可读性。同样,必要时,多行注释会更好。还可以使用注释来记录在哪里发生了功能丢失、错误或不完整。如果想遍历所有代码并且查看还有哪些东西不完整,则“TODO”字很有用 — 一个快速的 grep 命令将打印出所有 TODO 项。
不必注释每行代码,但是,请记住当调试或扩展程序时,注释是一种最好的资源。任何其它来源的程序员文档往往落后于实际代码,除非程序员非常勤奋。
注释循环和条件语句
应该象注释常规代码和函数一样注释循环和条件语句。对循环编号以便以后标识它们似乎有些过分。更好的方法是使用折叠编辑器,折叠循环时,它可以将整个循环显示成一行(折叠标记之间的行是隐藏的,但它们仍然存在)。考虑一下如 XML/HTML 开始/结束标记这样的折叠的标记,它们是可以嵌套的。您最喜爱的编辑器可能已经支持折叠。(X)Emacs 就支持折叠,它不是使用 Outline 就是使用 folding.el 方式。
清单 10
# go through all the numbers between 2 and 200, and print a message
# for each one
foreach my $counter (2 .. 200)
{
print "Whoa, the counter is $counter!\n";
}
总是陈述循环的目的和边界。例如,“count from 2 to 200”很好,但是“process array”则不好。如果逻辑条件影响边界,同样陈述它们,但不在循环的顶部。循环顶部的摘要不应该记录常规迭代的异常,除非它们对循环非常重要。您自己判断吧。
注释程序的最终阶段
在许多方面,程序的结尾是最繁琐的。工作已经完成,数据结构已经进入休眠状态(在 Perl 中不必担心内存释放),并且现在离结尾只差几行了。不要让这愚弄了您 — 程序的结束行可能与其它行一样危险。在这里,注释最不起眼的行,因为调试程序员所做的第一件事就是查看程序的退出行为。
清单 11
# delete old files, warn if they can't be removed
foreach (@myfiles)
{
unlink $_ or warn "Couldn't remove $_: $!";
}
print "whodunit.pl is done!\n"; # tell the user we're done
exit; # exit peacefully
编写程序的 POD 文档和帮助
旧式纯文档(Plain old documentation (POD))是将 Perl 脚本放入脚本本身的一种方法。 perldoc perlpod 命令将告诉您有关 POD 文档及其语法的更多信息。好的 POD 文档意味着用户可以快速和有效地访问程序的帮助。花时间学习 POD 语法:编写手册将更容易。另外,POD 与各种手册格式化程序兼容,因此可以从同一文档生成一个纯文本文件、UNIX 风格的帮助手册页和专业 LaTeX 文件。POD 是一个十分有限的格式,但是足够满足大多数文档的需要。
通常,下列节应该出现在 POD 文档中:NAME、SYNOPSIS、DESCRIPTION、OPTIONS、RETURN VALUE、ERRORS、DIAGNOSTICS、EXAMPLES、ENVIRONMENT、FILES、CAVEATS/WARNINGS、BUGS、RESTRICTIONS、NOTES、SEE ALSO、AUTHORS 以及 HISTORY(从 perldoc pod2man 中可以找到有关每节的更多信息;请记住这些是建议而不是命令)。
某些程序员对其程序设置 -h 开关以对程序调用 perldoc,因此打印出 POD 文档,就象用户输入 perldoc whodunit.pl 一样。这里的问题是用户不想从 -h 开关获取更多的额外信息。他只想要选项的摘要和列表。因此,更好的方法是编写从使用 -h 开关产生的单独帮助处理程序。
清单 12
# print_help: help handler, prints out help for whodunit.pl and exits
sub print_help
{
# print the help itself
print << EOHIPPUS;
This is help for the whodunit.pl program.
You can pass options to whodunit.pl as command-line arguments. For
example:
..../whodunit.pl -h
..../whodunit.pl -show suspects
List of options:
-h : print this help
-show : show the suspects, victims, or detectives (all of them if no
second argument is specified)
-quiet : print no information other than the killer's name
EOHIPPUS
exit; # do nothing else, just exit quietly
}
请注意 print_help 文档本身。POD 文档和其它联机帮助的外观同样也很重要。用户首先看的不是手册。使用 -h 标志或者查看 POD 文档会方便得多。注意冒号的对齐、行之间的空格和整体整洁。表面的外观往往比程序提供的实际功能更重要。编写得好的程序首要的是应该具有良好的文档。
某些程序员喜欢在程序中包含 POD 文档来代替常规注释。这样的 POD 注释自己用某行上的 =pod 开头(还有其它选项,在 perlpod 文档中解释),并且自己以 =cut 在某行结束。 =pod 行告诉 Perl 编译器停止解释每件事,直到 =cut 行为止,事实上从脚本本身排除了那个文本块。如果用户也是程序员,这当然好,但是如果普通用户只想查看脚本的文档而不是代码本身的注释,这可能会把他们弄糊涂。这个方法还将文档散布在整个代码中。应该限制它的使用。