您好,登錄后才能下訂單哦!
這篇文章主要介紹“怎么更好地重構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 examples
public 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)"]
// ? 只傳入一次users
return 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代碼”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。