本篇文章給大家?guī)砹岁P于php的相關知識,其中主要跟大家聊一聊什么是重構?怎么更好的重構PHP代碼?感興趣的朋友下面一起來看一下吧,希望對大家有幫助。
重構指的是在不改變原有功能的情況下,修改或者重新編寫代碼。
下面的例子中,我將向你展示如何更好地編寫代碼。
【資料圖】
#1 - 表現(xiàn)力
這可能只是一個簡單的技巧,但編寫富有表現(xiàn)力的代碼可以大大改進我們的代碼??偸亲尨a自我解釋,這樣未來的你或其他開發(fā)人員都能知道代碼中發(fā)生了什么。
不過也有開發(fā)人員表示,命名是編程中最困難的事情之一。這就是為什么這不像聽起來那么容易的原因之一。
示例 #1 - 命名
之前
// ? 這個方法是用來做什么的,方法名表達并不清晰// ? 是設置狀態(tài)還是檢查狀態(tài)呢?$status = $user->status("pending");之后
// ? 通過添加 is,使方法名表達的意圖更清晰// ? 檢測用戶狀態(tài)是否與給定狀態(tài)相等// ? 同時新變量名讓我們可以推斷它是布爾值$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方法說明了發(fā)生了什么事// ? 如果我們需要了解更多細節(jié),可以進入這個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(); }); // ? 很難說清此處發(fā)生了哪些行為 $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方法是你訪問該類首先會查看的方法 // ? 現(xiàn)在可以很容易就對這個方法做了些什么有個粗略的了解 $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);}// ? 如果需要了解更多細節(jié),可以查看這些專用的方法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: 有時你會聽到 “防衛(wèi)語句” 這樣的術語,它是通過提前返回實現(xiàn)。
#3 - 重構成集合 Collection
在 PHP 中,我們在很多不同數(shù)據(jù)中都用到了數(shù)組。處理及轉換這些數(shù)組可用功能非常有限,并且沒有提供良好的體驗。(array_walk, usort, etc)
要處理這個問題,有一個 Collection 類的概念,可用于幫你處理數(shù)組。最為人所知的是 Laravel 中的實現(xiàn),其中的 collection 類提供了許多有用的特性,用來處理數(shù)組。
注意: 以下例子, 我將使用 Laravel 的 collect () 輔助函數(shù),不過在其他框架或庫中的使用方式也很相似。
示例 #1
之前
// ? 這里我們有一個臨時變量 $score = 0;// ? 用循環(huán)沒有問題,不過可讀性還是有改善空間foreach($this->playedGames as $game) { $score += $game->score;}return $score;之后
// ? 集合是帶有方法的對象// ? sum 方法使之更具表現(xiàn)力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 進行排序處理的又是哪一個對象呢?它是如何實現(xiàn)?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() // ? 最后返回數(shù)組 ->toArray();#4 - 一致性
每一行代碼都會增加少量的視覺噪音。代碼越多,閱讀起來就越困難。這就是為什么制定規(guī)則很重要。保持類似的東西一致將幫助您識別代碼和模式。這將導致更少的噪聲和更可讀的代碼。
示例 #1
之前
class UserController { // ? 確定如何命名變量(駝峰或是蛇形等),不要混用! public function find($userId) { }}// ? 選擇使用單數(shù)或者復數(shù)形式命名控制器,并保持一致class InvoicesController { // ? 修改了樣式,如花扣號的位置,影響可讀性 public function find($user_id) { }}之后
class UserController { // ? 所有變量駝峰式命名 public function find($userId) { }}// ? 控制器命名規(guī)則一致(此處都使用單數(shù))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();之后
// ? 可通過接口提供通用規(guī)則保持一致性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... }}// ? 對類似的任務使用相同的方法名,更具可讀性// ? 不用再去查看類源碼,變可知它們都用在導出數(shù)據(jù)$pdfExport->export();$csvExporter->export();重構 ?? 測試
我已經(jīng)提到過重構不會改變代碼的功能。這在運行測試時很方便,因為它們也應該在重構之后工作。這就是為什么我只有在有測試的時候才開始重構代碼。他們將確保我不會無意中更改代碼的行為。所以別忘了寫測試,甚至去 TDD。
推薦學習:《PHP視頻教程》
以上就是教你如何更好地重構PHP代碼的詳細內容,更多請關注php中文網(wǎng)其它相關文章!
關鍵詞: