本篇文章给大家带来了关于php的相关知识,其中主要跟大家聊一聊什么是重构?怎么更好的重构PHP代码?感兴趣的朋友下面一起来看一下吧,希望对大家有帮助。
重构指的是在不改变原有功能的情况下,修改或者重新编写代码。
下面的例子中,我将向你展示如何更好地编写代码。
【资料图】
#1 - 表现力
这可能只是一个简单的技巧,但编写富有表现力的代码可以大大改进我们的代码。总是让代码自我解释,这样未来的你或其他开发人员都能知道代码中发生了什么。
不过也有开发人员表示,命名是编程中最困难的事情之一。这就是为什么这不像听起来那么容易的原因之一。
示例 #1 - 命名
之前
// ❌ 这个方法是用来做什么的,方法名表达并不清晰// ❌ 是设置状态还是检查状态呢?$status = $user->status("pending");
之后
// ✅ 通过添加 is,使方法名表达的意图更清晰// ✅ 检测用户状态是否与给定状态相等// ✅ 同时新变量名让我们可以推断它是布尔值$isUserPending = $user->isStatus("pending");
示例 #2 - 命名
之前
// ❌ 这个类返回的是什么?类名?类全名?还是类路径?return $factory->getTargetClass();
之后
// ✅ 我们获取的是类路径// ✅ 如果用户想要类名?则找错了方法return $factory->getTargetClassPath();
示例 #3 - 提取
之前
// ❌ 重复的代码 ( "file_get_contents", "base_path" 方法以及文件扩展)// ❌ 此刻,我们不去关心如何获得code examplespublic function setCodeExamples(string $exampleBefore, string $exampleAfter){ $this->exampleBefore = file_get_contents(base_path("$exampleBefore.md")); $this->exampleAfter = file_get_contents(base_path("$exampleAfter.md"));}
之后
public function setCodeExamples(string $exampleBefore, string $exampleAfter){ // ✅ 代码直接说明了我们的意图:获取code example(不关注如何获取) $this->exampleBefore = $this->getCodeExample($exampleBefore); $this->exampleAfter = $this->getCodeExample($exampleAfter);}// ✅ 这个新方法可多次调用private function getCodeExample(string $exampleName): string{ return file_get_contents(base_path("$exampleName.md"));}
示例 #4 - 提取
之前
// ❌ 多重 where 语句,使阅读变得困难// ❌ 意图究竟是什么呢?User::whereNotNull("subscribed")->where("status", "active");
之后
// ✅ 这个新的scope方法说明了发生了什么事// ✅ 如果我们需要了解更多细节,可以进入这个scope方法内部去了解// ✅ "subscribed" scope 方法可在其他地方使用User::subscribed();
示例 #5 - 提取
这是我之前项目的一个例子。我们用命令行导入用户。 ImportUsersCommand 类中含有一个 handle 方法,用来处理任务。
之前
protected function handle(){ // ❌ 这个方法包含太多代码 $url = $this->option("url") ?: $this->ask("Please provide the URL for the import:"); $importResponse = $this->http->get($url); // ❌ 进度条对用户很有用,不过却让代码显得杂乱 $bar = $this->output->createProgressBar($importResponse->count()); $bar->start(); $this->userRepository->truncate(); collect($importResponse->results)->each(function (array $attributes) use ($bar) { $this->userRepository->create($attributes); $bar->advance(); }); // ❌ 很难说清此处发生了哪些行为 $bar->finish(); $this->output->newLine(); $this->info("Thanks. Users have been imported."); if($this->option("with-backup")) { $this->storage ->disk("backups") ->put(date("Y-m-d")."-import.json", $response->body()); $this->info("Backup was stored successfully."); }}
之后
protected function handle(): void{ // ✅ handle方法是你访问该类首先会查看的方法 // ✅ 现在可以很容易就对这个方法做了些什么有个粗略的了解 $url = $this->option("url") ?: $this->ask("Please provide the URL for the import:"); $importResponse = $this->http->get($url); $this->importUsers($importResponse->results); $this->saveBackupIfAsked($importResponse);}// ✅ 如果需要了解更多细节,可以查看这些专用的方法protected function importUsers($userData): void{ $bar = $this->output->createProgressBar(count($userData)); $bar->start(); $this->userRepository->truncate(); collect($userData)->each(function (array $attributes) use ($bar) { $this->userRepository->create($attributes); $bar->advance(); }); $bar->finish(); $this->output->newLine(); $this->info("Thanks. Users have been imported.");}// ✅ 不要害怕使用多行代码// ✅ 这个例子中它让我们核心的 handle 方法更为简洁protected function saveBackupIfAsked(Response $response): void{ if($this->option("with-backup")) { $this->storage ->disk("backups") ->put(date("Y-m-d")."-import.json", $response->body()); $this->info("Backup was stored successfully."); }}
#2 - 提前返回
提前返回指的是,我们尝试通过将结构分解为特定 case 来避免嵌套的做法。这样,我们得到了更线性的代码,更易于阅读和了解。不要害怕使用多个 return 语句。
示例 #1
之前
public function calculateScore(User $user): int{ if ($user->inactive) { $score = 0; } else { // ❌ 怎么又有一个 "if"? if ($user->hasBonus) { $score = $user->score + $this->bonus; } else { // ❌ 由于存在多个层级,大费眼神 ? $score = $user->score; } } return $score;}
之后
public function calculateScore(User $user): int{ // ✅ 边缘用例提前检测 if ($user->inactive) { return 0; } // ✅ 每个用例都有自己的代码块,使得更容易跟进 if ($user->hasBonus) { return $user->score + $this->bonus; } return $user->score;}
示例 #2
之前
public function sendInvoice(Invoice $invoice): void{ if($user->notificationChannel === "Slack") { $this->notifier->slack($invoice); } else { // ❌ 即使是简单的ELSE都影响代码的可读性 $this->notifier->email($invoice); }}
之后
public function sendInvoice(Invoice $invoice): bool{ // ✅ 每个条件都易读 if($user->notificationChannel === "Slack") { return $this->notifier->slack($invoice); } // ✅ 不用再考虑ELSE 指向哪里 return $this->notifier->email($invoice);}
Note: 有时你会听到 “防卫语句” 这样的术语,它是通过提前返回实现。
#3 - 重构成集合 Collection
在 PHP 中,我们在很多不同数据中都用到了数组。处理及转换这些数组可用功能非常有限,并且没有提供良好的体验。(array_walk, usort, etc)
要处理这个问题,有一个 Collection 类的概念,可用于帮你处理数组。最为人所知的是 Laravel 中的实现,其中的 collection 类提供了许多有用的特性,用来处理数组。
注意: 以下例子, 我将使用 Laravel 的 collect () 辅助函数,不过在其他框架或库中的使用方式也很相似。
示例 #1
之前
// ❌ 这里我们有一个临时变量 $score = 0;// ❌ 用循环没有问题,不过可读性还是有改善空间foreach($this->playedGames as $game) { $score += $game->score;}return $score;
之后
// ✅ 集合是带有方法的对象// ✅ sum 方法使之更具表现力return collect($this->playedGames) ->sum("score");
示例 #2
之前
$users = [ [ "id" => 801, "name" => "Peter", "score" => 505, "active" => true], [ "id" => 844, "name" => "Mary", "score" => 704, "active" => true], [ "id" => 542, "name" => "Norman", "score" => 104, "active" => false],];// 请求结果: 只显示活跃用户,以 score 排序 ["Mary(704)","Peter(505)"]$users = array_filter($users, fn ($user) => $user["active"]);// ❌ usort 进行排序处理的又是哪一个对象呢?它是如何实现?usort($users, fn($a, $b) => $a["score"] < $b["score"]);// ❌ 所有的转换都是分离的,不过都是users相关的$userHighScoreTitles = array_map(fn($user) => $user["name"] . "(" . $user["score"] . ")", $users);return $userHighScoreTitles;
之后
$users = [ [ "id" => 801, "name" => "Peter", "score" => 505, "active" => true], [ "id" => 844, "name" => "Mary", "score" => 704, "active" => true], [ "id" => 542, "name" => "Norman", "score" => 104, "active" => false],];// 请求结果: 只显示活跃用户,以 score 排序 ["Mary(704)","Peter(505)"]// ✅ 只传入一次usersreturn collect($users) // ✅ 我们通过管道将其传入所有方法 ->filter(fn($user) => $user["active"]) ->sortBy("score") ->map(fn($user) => "{$user["name"]} ({$user["score"]})" ->values() // ✅ 最后返回数组 ->toArray();
#4 - 一致性
每一行代码都会增加少量的视觉噪音。代码越多,阅读起来就越困难。这就是为什么制定规则很重要。保持类似的东西一致将帮助您识别代码和模式。这将导致更少的噪声和更可读的代码。
示例 #1
之前
class UserController { // ❌ 确定如何命名变量(驼峰或是蛇形等),不要混用! public function find($userId) { }}// ❌ 选择使用单数或者复数形式命名控制器,并保持一致class InvoicesController { // ❌ 修改了样式,如花扣号的位置,影响可读性 public function find($user_id) { }}
之后
class UserController { // ✅ 所有变量驼峰式命名 public function find($userId) { }}// ✅ 控制器命名规则一致(此处都使用单数)class InvoiceController { // ✅ 花括号的位置(格式)一致,使代码更为可读 public function find($userId) { }}
示例 #2
之前
class PdfExporter{ // ❌ "handle" 和 "export" 是类似方法的不同名称 public function handle(Collection $items): void { // export items... }}class CsvExporter{ public function export(Collection $items): void { // export items... }}// ❌ 使用时你会疑惑它们是否处理相似的任务// ❌ 你可能需要再去查看类源码进行确定$pdfExport->handle();$csvExporter->export();
之后
// ✅ 可通过接口提供通用规则保持一致性interface Exporter{ public function export(Collection $items): void;}class PdfExporter implements Exporter{ public function export(Collection $items): void { // export items... }}class CsvExporter implements Exporter{ public function export(Collection $items): void { // export items... }}// ✅ 对类似的任务使用相同的方法名,更具可读性// ✅ 不用再去查看类源码,变可知它们都用在导出数据$pdfExport->export();$csvExporter->export();
重构 ❤️ 测试
我已经提到过重构不会改变代码的功能。这在运行测试时很方便,因为它们也应该在重构之后工作。这就是为什么我只有在有测试的时候才开始重构代码。他们将确保我不会无意中更改代码的行为。所以别忘了写测试,甚至去 TDD。
推荐学习:《PHP视频教程》
以上就是教你如何更好地重构PHP代码的详细内容,更多请关注php中文网其它相关文章!