Perl 完全指南 / 第 24 章:最佳实践
第 24 章:最佳实践
“代码是写给人看的,偶尔交给机器执行” — Harold Abelson
本章总结 Perl 开发中的代码风格、Modern Perl 理念以及生产环境的最佳实践。
24.1 代码风格规范
Perl Best Practices(PBP)
Damian Conway 的《Perl Best Practices》是 Perl 代码风格的经典参考:
| 规则 | 推荐 |
|---|
总是使用 use strict | ✅ 强制 |
总是使用 use warnings | ✅ 强制 |
使用 my 而非 our | 尽可能使用 my |
使用三参数 open | open my $fh, '<', $file |
检查 open 返回值 | or die 或 autodie |
使用 // 替代 ` | |
| 4 空格缩进 | 社区惯例 |
| 花括号独占一行或同行 | 一致即可 |
perltidy — 代码格式化
cpanm Perl::Tidy
# 格式化文件
perltidy script.pl
# 使用配置文件
# .perltidyrc
推荐的 .perltidyrc:
-l=100
-i=4
-ci=4
-bar
-baao
-boc
-et=4
-nolq
-nbl
-nsbl
-pt=1
-bt=1
-sbt=1
-cti=0
perlcritic — 代码质量检查
cpanm Perl::Critic
# 检查代码(5 级严格度)
perlcritic script.pl
perlcritic --severity 1 script.pl # 最严格
perlcritic --severity 5 script.pl # 仅严重问题
# 使用特定策略
perlcritic --verbose '%f:%l: %m (%s)\n' script.pl
# 生成配置文件
perlcritic --profile-proto > .perlcriticrc
推荐的 .perlcriticrc:
severity = 3
verbose = 8
[-ValuesAndExpressions::ProhibitConstantPragma]
[-Subroutines::ProhibitSubroutinePrototypes]
[TestingAndDebugging::RequireUseStrict]
severity = 1
[TestingAndDebugging::RequireUseWarnings]
severity = 1
24.2 Modern Perl 理念
Modern Perl 不仅是语法,更是一种工程方法论:
核心原则
| 原则 | 说明 |
|---|
use strict; use warnings; | 这不是可选的 |
use autodie; | 自动处理系统调用错误 |
use feature ':5.XX'; | 启用现代特性 |
use Moose 或 use Moo | 现代 OOP |
| 使用 CPAN | 不要重复造轮子 |
| 编写测试 | Test::More 是底线 |
| 使用版本控制 | Git |
| 使用依赖管理 | Carton / cpanfile |
推荐的脚本头部
#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use feature 'say';
use autodie;
# 如果是 Perl 5.38+
# use v5.38;
# 使用内置的 try/catch
Modern Perl 惯用法
# 1. 使用 say 替代 print ... "\n"
say "Hello, World!";
# 2. 使用 // 设默认值
my $name = $input // "anonymous";
# 3. 使用 //= 就地设置默认值
$count //= 0;
# 4. 使用 postfix dereference(Perl 5.24+)
my @items = $array_ref->@*;
my %data = $hash_ref->%*;
# 5. 使用 try/catch(Perl 5.38+)
use feature 'try';
try {
do_something();
} catch ($e) {
warn "Error: $e";
}
# 6. 使用 class(Perl 5.38+)
use feature 'class';
class Point {
field $x :param = 0;
field $y :param = 0;
}
24.3 命名规范
| 类型 | 推荐风格 | 示例 |
|---|
| 变量 | snake_case | $user_name, @file_list |
| 常量 | UPPER_CASE | MAX_RETRY, DEFAULT_PORT |
| 子程序 | snake_case | parse_input(), get_user() |
| 包名 | CamelCase | MyApp::Utils, HTTP::Tiny |
| 文件名 | snake_case | my_script.pl, utils.pm |
| 布尔变量 | is_/has_ 前缀 | $is_valid, $has_permission |
# 好的命名
my $user_count = get_user_count();
my $is_valid = validate_input($data);
my @active_users = grep { $_->is_active } @users;
# 不好的命名
my $x = get_uc();
my $flag = vi($data);
my @arr = grep { $_->ia } @arr2;
24.4 错误处理规范
# 1. 总是使用 Try::Tiny 或 try/catch
use Try::Tiny;
try {
do_risky_operation();
} catch {
log_error("操作失败: $_");
return;
};
# 2. 区分致命错误和非致命错误
sub read_config {
my ($file) = @_;
# 致命:配置文件必须存在
open my $fh, '<', $file or die "无法读取配置: $!\n";
# 非致命:可以跳过空行
my @lines;
while (my $line = <$fh>) {
chomp $line;
next if $line =~ /^\s*#/; # 跳过注释
next if $line =~ /^\s*$/; # 跳过空行
push @lines, $line;
}
close $fh;
return \@lines;
}
# 3. 使用 Carp 获取调用者信息
use Carp qw(croak confess);
sub divide {
my ($a, $b) = @_;
croak "Division by zero" unless $b;
return $a / $b;
}
24.5 日志规范
# 使用 Log::Log4perl(推荐)
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init({
level => $DEBUG,
layout => '%d [%p] %m%n',
file => ">> app.log",
});
my $log = get_logger();
# 使用
$log->debug("调试信息");
$log->info("处理请求");
$log->warn("警告:配置缺失");
$log->error("严重错误:数据库连接失败");
$log->fatal("致命错误,程序退出");
日志级别
| 级别 | 含义 | 使用场景 |
|---|
| TRACE | 最详细 | 开发调试 |
| DEBUG | 调试信息 | 开发和测试 |
| INFO | 一般信息 | 正常运行 |
| WARN | 警告 | 可能的问题 |
| ERROR | 错误 | 操作失败 |
| FATAL | 致命 | 程序无法继续 |
24.6 项目结构规范
推荐目录结构
myapp/
├── bin/ # 可执行脚本
│ └── myapp
├── lib/ # 库文件
│ ├── MyApp.pm
│ ├── MyApp/
│ │ ├── Config.pm
│ │ ├── Model/
│ │ ├── Controller/
│ │ └── Utils.pm
├── t/ # 测试
│ ├── 00-load.t
│ ├── 01-basic.t
│ └── author/
├── templates/ # 模板
├── public/ # 静态文件
├── config/ # 配置文件
├── script/ # 脚本
├── log/ # 日志
├── cpanfile # 依赖
├── cpanfile.snapshot # 依赖锁定
├── Changes # 变更日志
├── LICENSE
├── README.md
├── .gitignore
├── .perltidyrc
├── .perlcriticrc
├── Dockerfile
└── docker-compose.yml
24.7 生产环境检查清单
部署前检查
| 检查项 | 工具/方法 |
|---|
| 语法检查 | perl -c script.pl |
| 代码风格 | perltidy |
| 代码质量 | perlcritic --severity 3 |
| 测试通过 | prove -l t/ |
| POD 完整 | podchecker |
| 依赖声明 | cpanfile 完整 |
| 错误处理 | 无裸 open |
| 安全检查 | 无 SQL 注入、XSS |
| 性能分析 | nytprof |
生产环境配置
#!/usr/bin/env perl
use strict;
use warnings;
# 生产环境
$ENV{MOJO_MODE} = 'production';
# 关闭调试信息
$ENV{MOJO_LOG_LEVEL} = 'warn';
# 日志文件
use Log::Log4perl;
Log::Log4perl->init('/etc/myapp/log4perl.conf');
# 错误处理
$SIG{__DIE__} = sub {
my ($err) = @_;
Log::Log4perl->get_logger->fatal($err);
};
# 优雅退出
$SIG{TERM} = $SIG{INT} = sub {
print "收到终止信号,正在关闭...\n";
# 清理资源
exit 0;
};
24.8 安全最佳实践
输入验证
# 永远不信任用户输入
sub validate_email {
my ($email) = @_;
return unless defined $email;
return unless $email =~ /^[\w.+-]+\@[\w-]+\.[\w.]+$/;
return 1;
}
sub sanitize_input {
my ($input) = @_;
return "" unless defined $input;
$input =~ s/<[^>]*>//g; # 去除 HTML 标签
$input =~ s/[^\w\s.-]//g; # 只保留安全字符
return $input;
}
SQL 注入防护
# 错误!
$dbh->do("SELECT * FROM users WHERE name = '$name'");
# 正确!使用占位符
$dbh->do("SELECT * FROM users WHERE name = ?", undef, $name);
密码处理
use Crypt::Bcrypt qw(bcrypt_hash bcrypt_verify);
# 加密
my $hashed = bcrypt_hash({
password => $plain_password,
cost => 12,
});
# 验证
if (bcrypt_verify($input_password, $stored_hash)) {
say "密码正确";
}
24.9 代码审查要点
| 检查点 | 问题 |
|---|
use strict/warnings | 是否启用? |
| 变量作用域 | 是否有不必要的全局变量? |
| 错误处理 | 所有 I/O 是否有错误检查? |
| 输入验证 | 外部输入是否验证? |
| SQL 注入 | 是否使用占位符? |
| 正则表达式 | 是否有灾难性回溯风险? |
| 代码重复 | 是否可以提取公共函数? |
| 命名 | 变量名是否有意义? |
| 注释 | 复杂逻辑是否有注释? |
| 测试 | 关键路径是否有测试覆盖? |
本章小结
| 要点 | 内容 |
|---|
perltidy | 自动格式化代码 |
perlcritic | 静态代码质量检查 |
| Modern Perl | 使用现代特性、工具和方法论 |
| 错误处理 | 统一使用 Try::Tiny 或 try/catch |
| 日志 | 使用 Log::Log4perl 分级记录 |
| 安全 | 输入验证、SQL 注入防护、密码加密 |
| 项目结构 | 清晰的目录组织 |
练习
- 使用 perltidy 格式化你的代码
- 使用 perlcritic 检查你的项目,修复所有严重问题
- 为你的项目配置 .perltidyrc 和 .perlcriticrc
- 实现输入验证函数(邮箱、电话、URL)
- 设置 Log::Log4perl 配置,支持文件和控制台输出
扩展阅读