溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么更好地重構(gòu)PHP代碼

發(fā)布時(shí)間:2023-03-13 16:03:13 來(lái)源:億速云 閱讀:86 作者:iii 欄目:編程語(yǔ)言

這篇文章主要介紹“怎么更好地重構(gòu)PHP代碼”的相關(guān)知識(shí),小編通過(guò)實(shí)際案例向大家展示操作過(guò)程,操作方法簡(jiǎn)單快捷,實(shí)用性強(qiáng),希望這篇“怎么更好地重構(gòu)PHP代碼”文章能幫助大家解決問(wèn)題。

#1 - 表現(xiàn)力

這可能只是一個(gè)簡(jiǎn)單的技巧,但編寫富有表現(xiàn)力的代碼可以大大改進(jìn)我們的代碼??偸亲尨a自我解釋,這樣未來(lái)的你或其他開發(fā)人員都能知道代碼中發(fā)生了什么。

不過(guò)也有開發(fā)人員表示,命名是編程中最困難的事情之一。這就是為什么這不像聽起來(lái)那么容易的原因之一。

示例 #1 - 命名

之前

// ? 這個(gè)方法是用來(lái)做什么的,方法名表達(dá)并不清晰
// ? 是設(shè)置狀態(tài)還是檢查狀態(tài)呢?
$status = $user->status('pending');

之后

// ? 通過(guò)添加 is,使方法名表達(dá)的意圖更清晰
// ? 檢測(cè)用戶狀態(tài)是否與給定狀態(tài)相等
// ? 同時(shí)新變量名讓我們可以推斷它是布爾值
$isUserPending = $user->isStatus('pending');

示例 #2 - 命名

之前

// ? 這個(gè)類返回的是什么?類名?類全名?還是類路徑?
return $factory->getTargetClass();

之后

// ? 我們獲取的是類路徑
// ? 如果用戶想要類名?則找錯(cuò)了方法
return $factory->getTargetClassPath();

示例 #3 - 提取

之前

// ? 重復(fù)的代碼 ( "file_get_contents", "base_path" 方法以及文件擴(kuò)展)
// ? 此刻,我們不去關(guān)心如何獲得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)
{ 
    // ? 代碼直接說(shuō)明了我們的意圖:獲取code example(不關(guān)注如何獲取)
    $this->exampleBefore = $this->getCodeExample($exampleBefore);
    $this->exampleAfter = $this->getCodeExample($exampleAfter);
}
// ? 這個(gè)新方法可多次調(diào)用
private function getCodeExample(string $exampleName): string
{
    return file_get_contents(base_path("$exampleName.md"));
}

示例 #4 - 提取

之前

// ? 多重 where 語(yǔ)句,使閱讀變得困難
// ? 意圖究竟是什么呢?
User::whereNotNull('subscribed')->where('status', 'active');

之后

// ? 這個(gè)新的scope方法說(shuō)明了發(fā)生了什么事
// ? 如果我們需要了解更多細(xì)節(jié),可以進(jìn)入這個(gè)scope方法內(nèi)部去了解
// ? "subscribed" scope 方法可在其他地方使用
User::subscribed();

示例 #5 - 提取

這是我之前項(xiàng)目的一個(gè)例子。我們用命令行導(dǎo)入用戶。 ImportUsersCommand 類中含有一個(gè) handle 方法,用來(lái)處理任務(wù)。

之前

protected function handle()
{
    // ? 這個(gè)方法包含太多代碼
    $url = $this->option('url') ?: $this->ask('Please provide the URL for the import:');
    $importResponse =  $this->http->get($url);
    // ? 進(jìn)度條對(duì)用戶很有用,不過(guò)卻讓代碼顯得雜亂
    $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();
    });
    // ? 很難說(shuō)清此處發(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方法是你訪問(wèn)該類首先會(huì)查看的方法
    // ? 現(xiàn)在可以很容易就對(duì)這個(gè)方法做了些什么有個(gè)粗略的了解
    $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);
}
// ? 如果需要了解更多細(xì)節(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.');
}
// ? 不要害怕使用多行代碼
// ? 這個(gè)例子中它讓我們核心的 handle 方法更為簡(jiǎn)潔
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 - 提前返回

提前返回指的是,我們嘗試通過(guò)將結(jié)構(gòu)分解為特定 case 來(lái)避免嵌套的做法。這樣,我們得到了更線性的代碼,更易于閱讀和了解。不要害怕使用多個(gè) return 語(yǔ)句。

示例 #1

之前

public function calculateScore(User $user): int
{
    if ($user->inactive) {
        $score = 0;
    } else {
        // ? 怎么又有一個(gè) "if"?
        if ($user->hasBonus) {
            $score = $user->score + $this->bonus;
        } else {
            // ? 由于存在多個(gè)層級(jí),大費(fèi)眼神 ? 
            $score = $user->score;
        }
    }
    return $score;
}

之后

public function calculateScore(User $user): int
{
    // ? 邊緣用例提前檢測(cè)
    if ($user->inactive) {
        return 0;
    }
    // ? 每個(gè)用例都有自己的代碼塊,使得更容易跟進(jìn)
    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 {
        // ? 即使是簡(jiǎn)單的ELSE都影響代碼的可讀性
        $this->notifier->email($invoice);
    }
}

之后

public function sendInvoice(Invoice $invoice): bool
{
    // ? 每個(gè)條件都易讀
    if($user->notificationChannel === 'Slack')
    {
        return $this->notifier->slack($invoice);
    }
    // ? 不用再考慮ELSE 指向哪里
    return $this->notifier->email($invoice);
}

Note: 有時(shí)你會(huì)聽到 “防衛(wèi)語(yǔ)句” 這樣的術(shù)語(yǔ),它是通過(guò)提前返回實(shí)現(xiàn)。

#3 - 重構(gòu)成集合 Collection

在 PHP 中,我們?cè)诤芏嗖煌瑪?shù)據(jù)中都用到了數(shù)組。處理及轉(zhuǎn)換這些數(shù)組可用功能非常有限,并且沒(méi)有提供良好的體驗(yàn)。(array_walk, usort, etc)

要處理這個(gè)問(wèn)題,有一個(gè) Collection 類的概念,可用于幫你處理數(shù)組。最為人所知的是 Laravel 中的實(shí)現(xiàn),其中的 collection 類提供了許多有用的特性,用來(lái)處理數(shù)組。

注意: 以下例子, 我將使用 Laravel 的 collect () 輔助函數(shù),不過(guò)在其他框架或庫(kù)中的使用方式也很相似。

示例 #1

之前

// ? 這里我們有一個(gè)臨時(shí)變量 
$score = 0;
// ? 用循環(huán)沒(méi)有問(wèn)題,不過(guò)可讀性還是有改善空間
foreach($this->playedGames as $game) {
    $score += $game->score;
}
return $score;

之后

// ? 集合是帶有方法的對(duì)象
// ? 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],
];
// 請(qǐng)求結(jié)果: 只顯示活躍用戶,以 score 排序  ["Mary(704)","Peter(505)"]
$users = array_filter($users, fn ($user) => $user['active']);
// ? usort 進(jìn)行排序處理的又是哪一個(gè)對(duì)象呢?它是如何實(shí)現(xiàn)?
usort($users, fn($a, $b) => $a['score'] < $b['score']);
// ? 所有的轉(zhuǎn)換都是分離的,不過(guò)都是users相關(guān)的
$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],
];
// 請(qǐng)求結(jié)果: 只顯示活躍用戶,以 score 排序  ["Mary(704)","Peter(505)"]
// ? 只傳入一次users
return collect($users)
    // ? 我們通過(guò)管道將其傳入所有方法
  ->filter(fn($user) => $user['active'])
  ->sortBy('score')
  ->map(fn($user) => "{$user['name']} ({$user['score']})"
  ->values()
    // ? 最后返回?cái)?shù)組
  ->toArray();

#4 - 一致性

每一行代碼都會(huì)增加少量的視覺(jué)噪音。代碼越多,閱讀起來(lái)就越困難。這就是為什么制定規(guī)則很重要。保持類似的東西一致將幫助您識(shí)別代碼和模式。這將導(dǎo)致更少的噪聲和更可讀的代碼。

示例 #1

之前

class UserController 
{
    // ? 確定如何命名變量(駝峰或是蛇形等),不要混用!
    public function find($userId)
    {
    }
}
// ? 選擇使用單數(shù)或者復(fù)數(shù)形式命名控制器,并保持一致
class InvoicesController 
{
    // ? 修改了樣式,如花扣號(hào)的位置,影響可讀性
    public function find($user_id) {
    }
}

之后

class UserController 
{
    // ? 所有變量駝峰式命名
    public function find($userId)
    {
    }
}
// ? 控制器命名規(guī)則一致(此處都使用單數(shù))
class InvoiceController 
{
    // ? 花括號(hào)的位置(格式)一致,使代碼更為可讀
    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...
    }
}
// ? 使用時(shí)你會(huì)疑惑它們是否處理相似的任務(wù)
// ? 你可能需要再去查看類源碼進(jìn)行確定
$pdfExport->handle();
$csvExporter->export();

之后

// ? 可通過(guò)接口提供通用規(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...
    }
}
// ? 對(duì)類似的任務(wù)使用相同的方法名,更具可讀性
// ? 不用再去查看類源碼,變可知它們都用在導(dǎo)出數(shù)據(jù)
$pdfExport->export();
$csvExporter->export();

重構(gòu) ?? 測(cè)試

我已經(jīng)提到過(guò)重構(gòu)不會(huì)改變代碼的功能。這在運(yùn)行測(cè)試時(shí)很方便,因?yàn)樗鼈円矐?yīng)該在重構(gòu)之后工作。這就是為什么我只有在有測(cè)試的時(shí)候才開始重構(gòu)代碼。他們將確保我不會(huì)無(wú)意中更改代碼的行為。所以別忘了寫測(cè)試,甚至去 TDD。

關(guān)于“怎么更好地重構(gòu)PHP代碼”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

php
AI