IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Linux  >

更佳编程之路: 第二章

注释代码

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Teodor Zlatanov (tzz@iglou.com), 程序员, Gold Software Systems

2001 年 11 月 12 日

developerWorks 上的这个系列文章包含使用 Perl进行更佳编程的完整指南。在这第二部分中,Teodor仔细研究了代码中的注释。对于软件团队的长期目标来说,程序代码中的注释可能与实际代码本身一样重要。不幸的是,它们经常也是最容易被忽视的。通过技巧、讲解、示例和轶事,Teodor从头到尾深入讨论了对程序语言进行注释的迫切本质。

从来没有文档太多这回事。清晰常常意味着重复。将您的代码看成是向世界展示的东西。世界上有许多人。您认为冗余的注释可能对某人大有用处。五年之后,当您添加新特性时,它甚至可能对 大有用处。

基本注释

编写程序时,使用良好的规划。不必事先确定每个细节,但是应该将程序分成几个组成部分,并且使用注释来填充间隙。

下列是我个人的编码风格。您可能不喜欢它,但是请客观地看待它,并且看看有什么可以为您及您的团队所用。

首先,考虑一下该注释的预期读者。尽量使注释足够清晰,以便第三方顾问领会。代码越复杂,就应该添加越多的注释以阐明目的。不要将注释放在以后再做;让它们成为您思考过程的一部分:问题、解决方案、注释,然后调试。在调试之前创建注释尤为重要。您自己代码中的注释将有助于更好和更快地调试。

有时不仅陈述问题的解决方案很有帮助,而且陈述问题本身也是很有帮助的。例如:

清单 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 行为止,事实上从脚本本身排除了那个文本块。如果用户也是程序员,这当然好,但是如果普通用户只想查看脚本的文档而不是代码本身的注释,这可能会把他们弄糊涂。这个方法还将文档散布在整个代码中。应该限制它的使用。



参考资料



关于作者

Teodor Zlatanov

Teodor Zlatanov 1999 年毕业于波士顿大学,获计算机工程理学硕士学位。从 1992 开始,他就成为了一名程序员,使用 Perl、Java、C 和 C++。他对文本解析方面、三层客户机—服务器数据库体系结构、UNIX 系统管理、CORBA 和项目管理的开放源码工作都很感兴趣。可以通过 tzz@iglou.com与 Teodor 联系。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款