强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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
使用三参数 openopen my $fh, '<', $file
检查 open 返回值or dieautodie
使用 // 替代 `
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 Mooseuse 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/catchPerl 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_CASEMAX_RETRY, DEFAULT_PORT
子程序snake_caseparse_input(), get_user()
包名CamelCaseMyApp::Utils, HTTP::Tiny
文件名snake_casemy_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 注入防护、密码加密
项目结构清晰的目录组织

练习

  1. 使用 perltidy 格式化你的代码
  2. 使用 perlcritic 检查你的项目,修复所有严重问题
  3. 为你的项目配置 .perltidyrc 和 .perlcriticrc
  4. 实现输入验证函数(邮箱、电话、URL)
  5. 设置 Log::Log4perl 配置,支持文件和控制台输出

扩展阅读