遗留 PHP 代码重构:高级水平
处理 PHP 中的遗留代码是许多开发者不可避免的现实。这类代码通常是一个没有测试、存在全局依赖、业务逻辑与视图混杂的单一应用。简单的“凭感觉”重构可能导致致命错误。在本文中,我们将探讨高级重构技术,这些技术能够安全且高效地现代化遗留系统。
1. “Seam”策略:创建断点
重构遗留代码的第一步是创建接缝 (seams)。接缝是一个可以在不直接编辑代码的情况下改变其行为的地方。当代码没有测试覆盖且无法隔离运行时,这一点尤为重要。
考虑一个经典示例:在方法内部直接操作数据库。
<?php// 遗留代码class UserReport { public function getActiveUsers() { $db = new PDO('mysql:host=localhost;dbname=test', 'root', ''); $stmt = $db->query("SELECT * FROM users WHERE active = 1"); return $stmt->fetchAll(); }}?>问题:new PDO(...) 创建了一个硬依赖。要测试此方法,需要有一个真实的数据库。
解决方案:通过构造函数注入依赖。
<?php// 重构:通过 DI 创建接缝class UserReport { private PDO $pdo;
public function __construct(PDO $pdo) { $this->pdo = $pdo; }
public function getActiveUsers(): array { $stmt = $this->pdo->query("SELECT * FROM users WHERE active = 1"); return $stmt->fetchAll(); }}
// 使用$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');$report = new UserReport($pdo);?>现在你可以在测试中将 PDO 替换为模拟对象。这是一个简单但有效的接缝。
2. “Sprout Method”和“Sprout Class”模式
当你无法修改现有方法时(例如,由于缺少测试),请使用“Sprout”策略——在旧代码旁边创建新代码,逐步切换调用。
示例:我们有一个巨大的方法,用于验证和保存用户。
<?phpclass UserService { public function saveUser(array $data): bool { // 100 行验证、日志记录和保存代码 $email = $data['email'] ?? ''; if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email'); } // ... 许多其他代码 $db = new PDO(...); $stmt = $db->prepare("INSERT INTO users ..."); // ... }}?>与其立即重构整个方法,不如创建一个新的验证类。
<?phpclass UserValidator { public function validate(array $data): void { if (!filter_var($data['email'] ?? '', FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email'); } // 添加其他检查 }}
class UserService { private UserValidator $validator;
public function __construct(UserValidator $validator) { $this->validator = $validator; }
public function saveUser(array $data): bool { $this->validator->validate($data); // 其余代码暂时保留 }}?>现在你可以为 UserValidator 编写单元测试,而无需改动旧代码。你将逐步将逻辑从 saveUser 中提取到新类中。
3. 消除全局状态和静态方法
遗留代码经常使用全局变量、单例和静态方法。这使得代码几乎无法...