perlmodstyle - Perl 模块风格指南
本文档试图描述 Perl 社区的编写 Perl 模块的“最佳实践”。它扩展了 perlstyle 中的建议,在阅读本文档之前应将其视为必读内容。
虽然本文档旨在对所有模块作者有用,但它特别针对希望在 CPAN 上发布其模块的作者。
重点在于对模块用户可见的样式元素,而不是只有模块开发者才能看到的那些部分。但是,本文档中提出的许多准则都可以推断并成功应用于模块的内部结构。
本文档与 perlnewmod 的不同之处在于,它是一个风格指南,而不是创建 CPAN 模块的教程。它提供了一个清单,可以根据该清单比较模块以确定它们是否符合最佳实践,而不必详细描述如何实现这一点。
本文档中包含的所有建议都是从与经验丰富的 CPAN 作者和用户进行广泛的对话中收集的。这里给出的每一条建议都是以前错误的结果。此信息旨在帮助您避免犯同样的错误以及为修复错误而不可避免地需要做的额外工作。
本文档的第一部分提供了一个细化的清单;后续部分提供了对清单中项目更详细的讨论。最后一部分“常见陷阱”描述了 CPAN 作者犯的一些最常见的错误。
有关此清单中每个项目的更多详细信息,请参见下文。
不要重复造轮子
尽可能修补、扩展或子类化现有模块
专心做好一件事
选择一个适当的名称
发布之前征求反馈
API 应能让一般的程序员理解
简单任务使用简单的方法
将功能与输出分开
子例程或方法的命名一致
当参数多于两个时,使用命名参数(哈希或哈希引用)
确保您的模块在 use strict
和 -w
下工作
稳定的模块应保持向后兼容性
使用 POD 编写文档
文档目的、范围和目标应用程序
记录每个公开可访问的方法或子例程,包括参数和返回值
在文档中提供使用示例
提供一个 README 文件,还可能发布说明、变更日志等
提供指向更多信息的链接(URL、电子邮件)
在 Makefile.PL 或 Build.PL 中指定先决条件
使用 use
指定 Perl 版本要求
在模块中包含测试
选择一个明智且一致的版本编号方案(X.YY 是常见的 Perl 模块编号方案)
无论更改多么小,都要增加版本号
使用“make dist”打包模块
选择合适的许可证(GPL/Artistic 是一个不错的默认选项)
尝试不要在不花时间思考的情况下直接开始开发模块。稍加考虑可能会为你以后节省大量精力。
你甚至可能不需要编写模块。检查它是否已在 Perl 中完成,并且除非你有充分的理由,否则避免重新发明轮子。
寻找预先存在的模块的不错地方包括 MetaCPAN 和在 [email protected]
(https://lists.perl.org/list/module-authors.html) 上提问。
如果现有模块几乎可以满足你的需求,请考虑编写补丁、编写子类或以其他方式扩展现有模块,而不是重写它。
冒着陈述显而易见的事实的风险,模块旨在成为模块化的。Perl 开发人员应该能够使用模块来组合其应用程序的构建块。然而,重要的是这些块具有正确的形状,并且开发人员不必在只需要小块时使用大块。
你的模块应该有一个明确定义的范围,不超过一个句子。你的模块可以分解成一个相关模块系列吗?
糟糕的示例
“FooBar.pm 提供了 FOO 协议和相关 BAR 标准的实现。”
好的示例
“Foo.pm 提供了 FOO 协议的实现。Bar.pm 实现了相关的 BAR 协议。”
这意味着,如果开发人员只需要一个用于 BAR 标准的模块,则不应该强制他们也安装 FOO 的库。
确保尽早为模块选择一个合适的名称。这将帮助人们查找和记住您的模块,并使使用模块进行编程更加直观。
为模块命名时,请考虑以下事项
具有描述性(即准确描述模块的用途)。
与现有模块保持一致。
反映模块的功能,而不是实现。
避免启动新的顶级层次结构,尤其是在已经存在适合的层次结构可以放置模块的情况下。
如果您之前从未将模块上传到 CPAN(即使您上传过),我们强烈建议您从已经熟悉模块的应用程序域和 CPAN 命名系统的人那里获取反馈。类似模块或具有类似名称的模块的作者可能是开始的好地方,例如 Perl Monks 等社区网站。
模块设计和编码的注意事项
您的模块可以面向对象 (OO),也可以不面向对象,或者它可以同时提供两种类型的接口。每种技术都有利有弊,在设计 API 时应考虑这些利弊。
在《Perl 最佳实践》(版权所有 2004 年,由 O'Reilly Media, Inc. 出版)中,Damian Conway 提供了一系列标准,供在决定 OO 是否适合您的问题时使用
正在设计的系统很大,或者可能变得很大。
数据可以聚合到明显的结构中,尤其是当每个聚合中都有大量数据时。
各种类型的数据聚合形成一个自然层次结构,便于使用继承和多态性。
您有一条数据,对其应用了许多不同的操作。
您需要对相关类型的数据执行相同的一般操作,但根据操作应用于数据的特定类型略有不同。
您以后可能需要添加新的数据类型。
数据块之间的典型交互最好用运算符表示。
系统各个组件的实现可能会随着时间而改变。
系统设计已经是面向对象的。
大量其他程序员将使用您的代码模块。
仔细考虑面向对象是否适合您的模块。无端的面向对象会导致复杂的 API,普通模块用户难以理解或使用。
您的界面应该能让普通的 Perl 程序员理解。以下准则可以帮助您判断您的 API 是否足够直接
拥有大量简单的例程比拥有几个庞大的例程更好。如果您的例程根据其参数显著改变其行为,则表明您应该有两个(或更多)单独的例程。
以最通用的形式返回您的结果,并允许用户选择如何使用它们。最通用的形式通常是 Perl 数据结构,然后可以使用它来生成文本报告、HTML、XML、数据库查询或您的用户需要的任何其他内容。
如果您的例程遍历某种列表(例如文件列表或数据库中的记录),您可以考虑提供一个回调,以便用户可以依次操作列表的每个元素。File::Find 通过其 find(\&wanted, $dir)
语法提供了一个示例。
不要要求每个模块用户都通过相同的步骤来实现一个简单的结果。您始终可以为更复杂或非标准的行为包括可选参数或例程。如果大多数用户在开始使用您的模块时必须键入几行几乎相同的代码,则表明您应该将该行为设为默认值。另一个表明您应该使用默认值的良好指标是,如果大多数用户使用相同的参数调用您的例程。
您的命名应该一致。例如,最好有
display_day();
display_week();
display_year();
比
display_day();
week_display();
show_year();
这同样适用于方法名称、参数名称以及对用户可见的任何其他内容(以及大多数不可见的内容!)
使用命名参数。使用此类哈希更容易
$obj->do_something(
name => "wibble",
type => "text",
size => 1024,
);
... 而不是像这样使用长长的未命名参数列表
$obj->do_something("wibble", "text", 1024);
虽然参数列表可能适用于一个、两个甚至三个参数,但更多参数会让模块用户难以记住,也让模块作者难以管理。如果您想添加一个新参数,则必须将其添加到列表末尾以保持向后兼容性,这可能会让您的列表顺序不直观。此外,如果许多元素可能未定义,您可能会看到以下难看的函数调用
$obj->do_something(undef, undef, undef, undef, undef, 1024);
为具有参数的参数提供合理的默认值。不要让您的用户指定几乎总是相同的参数。
是将参数传递到哈希还是哈希引用中,这个问题在很大程度上取决于个人风格。
使用以连字符(-name
)或完全大写(NAME
)开头的哈希键是旧版 Perl 的遗留问题,其中普通小写字符串不能由 =>
运算符正确处理。虽然出于历史原因或个人风格,一些模块保留了大写或带连字符的参数键,但大多数新模块应使用简单的低级键。无论您选择什么,都要保持一致!
您的模块应在 strict 严格模式下成功运行,并且应在不生成任何警告的情况下运行。您的模块还应在适当的情况下处理污点检查,尽管在许多情况下这可能会造成困难。
“稳定”的模块不应破坏向后兼容性,除非至少有一个较长的过渡阶段和版本号的重大更改。
当您的模块遇到错误时,它应执行以下一项或多项操作
返回一个未定义的值。
设置 $Module::errstr
或类似内容(errstr
是 DBI 和其他流行模块使用的常见名称;如果您选择其他内容,请务必清楚地记录它)。
向 STDERR warn()
或 carp()
一条消息。
仅当模块绝对无法弄清楚要做什么时,才 croak()
。(croak()
是 die()
的一个更好的版本,用于模块中,它从调用者的角度报告其错误。有关 croak()
、carp()
和其他有用例程的详细信息,请参阅 Carp。)
作为上述方法的替代方案,您可能更喜欢使用 Error 模块抛出异常。
可配置的错误处理对您的用户非常有用。考虑为警告和调试消息提供级别选择、将消息发送到单独文件、指定错误处理例程或其他此类功能的方法。请务必将所有这些选项默认为最常见的用法。
您的模块应包括针对 Perl 开发人员的文档。您应该为您的常规技术文档使用 Perl 的“普通旧文档”(POD),尽管您可能希望以其他格式编写附加文档(白皮书、教程等)。您需要涵盖以下主题
模块的常见用途的概要
模块的目的、范围和目标应用程序
每个可公开访问的方法或子例程的使用,包括参数和返回值
使用示例
进一步信息的来源
作者/维护者的联系电子邮件地址
Perl 模块文档中的详细程度通常从较少详细到更详细。您的 SYNOPSIS 部分应包含一个最小的使用示例(可能只有短短一行代码;跳过不常见的用例或大多数用户不需要的任何内容);DESCRIPTION 应以广泛的术语描述您的模块,通常只有几段;模块例程或方法的更多详细信息、冗长的代码示例或其他深入的材料应在后续部分中给出。
理想情况下,稍微熟悉您的模块的人应该能够在不按“向下翻页”的情况下刷新他们的记忆。随着您的读者继续阅读文档,他们应该获得越来越多的知识。
Perl 模块文档中建议的章节顺序为
名称
SYNOPSIS
DESCRIPTION
一个或多个章节或小节,详细说明可用方法和例程以及任何其他相关信息。
BUGS/CAVEATS/etc
作者
另请参阅
COPYRIGHT and LICENSE
将文档放在它所记录的代码附近(“内联”文档)。在给定方法的子例程正上方包含 POD。这使得保持文档最新变得更加容易,并且避免了对每段代码进行两次记录(一次在 POD 中,一次在注释中)。
模块还应包含一个 README 文件,描述该模块并提供指向更多信息的指针(网站、作者电子邮件)。
应包含一个 INSTALL 文件,其中应包含简单的安装说明。当使用 ExtUtils::MakeMaker 时,这通常为
当使用 Module::Build 时,这通常为
应为每次软件发行制作发行说明或变更日志,描述用户可见的模块更改,并使用与用户相关的术语。
除非你有充分的理由使用其他格式(例如公司内部使用的格式),否则惯例是将变更日志文件命名为 Changes
,并遵循 CPAN::Changes::Spec 中描述的简单格式。
版本号应至少指示主要版本和次要版本,还可能指示次次要版本。主要版本是其中大部分功能已更改或其中添加了主要新功能的版本。次要版本是其中添加或更改了少量功能的版本。次次要版本号通常用于不影响功能的更改,例如文档修补程序。
最常见的 CPAN 版本编号方案如下所示
1.00, 1.10, 1.11, 1.20, 1.30, 1.31, 1.32
正确的 CPAN 版本号是一个浮点数,小数点后至少有 2 位数字。你可以使用以下方法测试它是否符合 CPAN
perl -MExtUtils::MakeMaker -le 'print MM->parse_version(shift)' \
'Foo.pm'
如果你想发布模块的“测试版”或“早期版本”,但不想让 CPAN.pm 将其列为最新版本,请在常规版本号后使用“_”,后跟至少 2 位数字,例如 1.20_01。如果你这样做,建议使用以下惯用语
our $VERSION = "1.12_01"; # so CPAN distribution will have
# right filename
our $XS_VERSION = $VERSION; # only needed if you have XS code
$VERSION = eval $VERSION; # so "use Module 0.002" won't warn on
# underscore
利用此技巧,MakeMaker 将仅读取第一行,从而读取下划线,而 Perl 解释器将评估 $VERSION 并将字符串转换为数字。稍后将 $VERSION 视为数字的运算将能够执行此操作,而不会引发关于 $VERSION 不是数字的警告。
切勿发布任何内容(即使是一字文档补丁),而不增加数字。即使是一字文档补丁也应导致次要级别版本发生更改。
一旦选定,坚持您的版本方案非常重要,不要减少数字位数。这是因为“下游”打包程序(例如 FreeBSD 端口系统)以各种方式解释版本号。如果您更改版本方案中的数字位数,您可能会混淆这些系统,从而使您的模块版本混乱,这显然是不好的。
模块作者应仔细考虑是否依赖其他模块,以及依赖哪些模块。
最重要的是,选择尽可能稳定的模块。按优先顺序
核心 Perl 模块
稳定的 CPAN 模块
不稳定的 CPAN 模块
无法从 CPAN 获得的模块
在 Makefile.PL 或 Build.PL 中的先决条件中指定其他 Perl 模块的版本要求。
务必在 Makefile.PL 或 Build.PL 中指定 Perl 版本要求,并使用 require 5.6.1
或类似命令。有关详细信息,请参阅 use VERSION
的文档。
在分发之前应测试所有模块(使用“make disttest”),并且还应向安装模块的人员提供测试(使用“make test”)。对于 Module::Build,您将使用 make test
等效项 perl Build test
。
这些测试的重要性与模块声称的稳定性成正比。声称稳定或希望得到广泛使用的模块应尽可能遵守严格的测试方案。
对帮助您编写测试(对您的开发过程或时间影响最小)有用的模块包括 Test::Simple、Carp::Assert 和 Test::Inline。对于更复杂的测试套件,有 Test::More 和 Test::MockObject。
应使用标准打包工具之一打包模块。目前,您可以在 ExtUtils::MakeMaker 和更独立于平台的 Module::Build 之间进行选择,从而允许以一致的方式安装模块。使用 ExtUtils::MakeMaker 时,您可以使用“make dist”创建您的包。存在一些工具可帮助您以 MakeMaker 友好的方式构建模块。其中包括 ExtUtils::ModuleMaker 和 h2xs。另请参阅 perlnewmod。
确保你的模块有许可证,并且其全文包含在发行版中(除非它是常见的,并且许可证条款不要求你包含它)。
如果你不知道使用什么许可证,那么在 GPL 和 Artistic 许可证(与 Perl 自身相同)下进行双重许可是一个好主意。请参见 perlgpl 和 perlartistic。
CPAN 已经非常非常完善地服务于某些应用程序空间。模板系统就是一个示例,日期和时间模块是另一个示例,还有很多其他示例。虽然编写这些内容的自己的版本是一种惯例,但请仔细考虑 Perl 世界是否真的需要你发布它。
你的模块将成为开发人员工具包的一部分。它本身不会形成整个工具包。在你的代码成为一个整体系统而不是一组模块化构建块之前,添加额外功能很诱人。
不要陷入为错误受众写作的陷阱。你的主要受众是具有至少中等程度理解你的模块的应用程序域的经验丰富的开发人员,他们刚刚下载了你的模块,并希望尽快开始使用它。
教程、最终用户文档、研究论文、常见问题解答等不适用于模块的主要文档。如果你真的想编写这些内容,请将它们作为子文档包含在内,例如 My::Module::Tutorial
或 My::Module::FAQ
,并在主要文档的另请参阅部分中提供链接。
通用 Perl 样式指南
如何创建新模块
POD 文档
验证你的 POD 是否正确
Test::Simple、Test::Inline、Carp::Assert、Test::More、Test::MockObject
Perl 作者上传服务器。包含模块作者信息链接。
Kirrily "Skud" Robert <[email protected]>