>백엔드 개발 >PHP 튜토리얼 >PHP 코드를 더 잘 리팩토링하는 방법을 가르쳐주세요.

PHP 코드를 더 잘 리팩토링하는 방법을 가르쳐주세요.

藏色散人
藏色散人앞으로
2023-03-13 15:44:273001검색

이 기사는 PHP에 대한 관련 지식을 제공합니다. 주로 리팩토링이 무엇인지 설명합니다. PHP 코드를 더 잘 리팩토링하는 방법은 무엇입니까? 관심 있는 친구들은 아래를 살펴보시면 모두에게 도움이 되길 바랍니다.

PHP 코드를 더 잘 리팩토링하는 방법을 가르쳐주세요.

리팩토링은 원래 기능을 변경하지 않고 코드를 수정하거나 다시 작성하는 것을 의미합니다.

아래 예시에서는 코드를 더 잘 작성하는 방법을 보여드리겠습니다.

#1 - 표현력

이것은 단순한 팁일 수도 있지만 표현력 있는 코드를 작성하면 코드가 크게 향상될 수 있습니다. 미래에 귀하나 다른 개발자가 코드에서 무슨 일이 일어나고 있는지 알 수 있도록 항상 코드를 설명이 가능하도록 만드십시오.

그러나 일부 개발자들은 프로그래밍에서 가장 어려운 일 중 하나가 이름 짓는 일이라고 말했습니다. 이것이 말처럼 쉽지 않은 이유 중 하나입니다.

예제 #1 - 이름 지정

before

// ❌ 这个方法是用来做什么的,方法名表达并不清晰
// ❌ 是设置状态还是检查状态呢?
$status = $user->status('pending');

after

// ✅ 通过添加 is,使方法名表达的意图更清晰
// ✅ 检测用户状态是否与给定状态相等
// ✅ 同时新变量名让我们可以推断它是布尔值
$isUserPending = $user->isStatus('pending');

예제 #2 - 이름 지정

before

// ❌ 这个类返回的是什么?类名?类全名?还是类路径?
return $factory->getTargetClass();

after

// ✅ 我们获取的是类路径
// ✅ 如果用户想要类名?则找错了方法
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 -

Before

// ❌ 多重 where 语句,使阅读变得困难
// ❌ 意图究竟是什么呢?
User::whereNotNull('subscribed')->where('status', 'active');

After

// ✅ 这个新的scope方法说明了发生了什么事
// ✅ 如果我们需要了解更多细节,可以进入这个scope方法内部去了解
// ✅ "subscribed" scope 方法可在其他地方使用
User::subscribed();

Example #5 -

추출 이것은 이전 프로젝트의 예입니다. 명령줄을 사용하여 사용자를 가져옵니다. ImportUsersCommand 클래스에는 작업을 처리하는 핸들 메서드가 포함되어 있습니다.

Before

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.');
    }
}

After

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 - Early Return

Early Return은 구조를 특정 케이스로 쪼개서 중첩을 피하려고 노력하는 관행을 말합니다. 이렇게 하면 더 쉽게 읽고 이해할 수 있는 선형 코드를 얻을 수 있습니다. 여러 개의 return 문을 사용하는 것을 두려워하지 마세요.

Example #1

Before

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;
}

After

public function calculateScore(User $user): int
{
    // ✅ 边缘用例提前检测
    if ($user->inactive) {
        return 0;
    }
    // ✅ 每个用例都有自己的代码块,使得更容易跟进
    if ($user->hasBonus) {
        return $user->score + $this->bonus;
    }
    return $user->score;
}

Example #2

Before

public function sendInvoice(Invoice $invoice): void
{
    if($user->notificationChannel === 'Slack')
    {
        $this->notifier->slack($invoice);
    } else {
        // ❌ 即使是简单的ELSE都影响代码的可读性
        $this->notifier->email($invoice);
    }
}

After

public function sendInvoice(Invoice $invoice): bool
{
    // ✅ 每个条件都易读
    if($user->notificationChannel === 'Slack')
    {
        return $this->notifier->slack($invoice);
    }
    // ✅ 不用再考虑ELSE 指向哪里
    return $this->notifier->email($invoice);
}

참고: 조기 복귀 달성에 의해 반환되는 "방어 진술"이라는 용어를 가끔 듣게 됩니다.

#3 - 컬렉션으로 재구성

PHP에서는 다양한 데이터에 배열을 사용합니다. 이러한 어레이를 처리하고 변환하는 데 사용할 수 있는 기능은 매우 제한되어 있으며 좋은 경험을 제공하지 않습니다. (array_walk, usort, etc)

이 문제를 해결하기 위해 배열을 처리하는 데 도움이 될 수 있는 Collection 클래스 개념이 있습니다. 가장 잘 알려진 구현은 Laravel에 있으며, 여기서 컬렉션 클래스는 배열 작업에 유용한 많은 기능을 제공합니다.

참고: 다음 예제에서는 Laravel의 Collect() 도우미 기능을 사용하지만 다른 프레임워크나 라이브러리에서도 비슷한 방식으로 사용할 수 있습니다.

예제 #1

Before

// ❌ 这里我们有一个临时变量 
$score = 0;
// ❌ 用循环没有问题,不过可读性还是有改善空间
foreach($this->playedGames as $game) {
    $score += $game->score;
}
return $score;

After

// ✅ 集合是带有方法的对象
// ✅ sum 方法使之更具表现力
return collect($this->playedGames)
    ->sum('score');

예제 #2

Before

$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[&#39;score&#39;] < $b[&#39;score&#39;]);
// ❌ 所有的转换都是分离的,不过都是users相关的
$userHighScoreTitles = array_map(fn($user) => $user[&#39;name&#39;] . &#39;(&#39; . $user[&#39;score&#39;] . &#39;)&#39;, $users);
return $userHighScoreTitles;

After

$users = [
    [ &#39;id&#39; => 801, &#39;name&#39; => &#39;Peter&#39;, &#39;score&#39; => 505, &#39;active&#39; => true],
    [ &#39;id&#39; => 844, &#39;name&#39; => &#39;Mary&#39;, &#39;score&#39; => 704, &#39;active&#39; => true],
    [ &#39;id&#39; => 542, &#39;name&#39; => &#39;Norman&#39;, &#39;score&#39; => 104, &#39;active&#39; => false],
];
// 请求结果: 只显示活跃用户,以 score 排序  ["Mary(704)","Peter(505)"]
// ✅ 只传入一次users
return collect($users)
    // ✅ 我们通过管道将其传入所有方法
  ->filter(fn($user) => $user[&#39;active&#39;])
  ->sortBy(&#39;score&#39;)
  ->map(fn($user) => "{$user[&#39;name&#39;]} ({$user[&#39;score&#39;]})"
  ->values()
    // ✅ 最后返回数组
  ->toArray();

#4 - 일관성

모든 코드 줄에는 약간의 시각적 노이즈가 추가됩니다. . 코드가 많을수록 읽기가 더 어려워집니다. 그렇기 때문에 규칙을 갖는 것이 중요합니다. 이와 같은 것을 일관되게 유지하면 코드와 패턴을 인식하는 데 도움이 됩니다. 이렇게 하면 노이즈가 줄어들고 코드 읽기가 쉬워집니다.

Example #1

Before

class UserController 
{
    // ❌ 确定如何命名变量(驼峰或是蛇形等),不要混用!
    public function find($userId)
    {
    }
}
// ❌ 选择使用单数或者复数形式命名控制器,并保持一致
class InvoicesController 
{
    // ❌ 修改了样式,如花扣号的位置,影响可读性
    public function find($user_id) {
    }
}

After

class UserController 
{
    // ✅ 所有变量驼峰式命名
    public function find($userId)
    {
    }
}
// ✅ 控制器命名规则一致(此处都使用单数)
class InvoiceController 
{
    // ✅ 花括号的位置(格式)一致,使代码更为可读
    public function find($userId)
    {
    }
}

Example #2

Before

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();

After

// ✅ 可通过接口提供通用规则保持一致性
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();

Refactor❤️ Test

리팩토링은 변하지 않는다고 이미 언급했는데 코드 기능. 이는 리팩토링 후에도 작동해야 하므로 테스트를 실행할 때 편리합니다. 그래서 테스트가 있을 때만 코드 리팩토링을 시작합니다. 그들은 내가 실수로 코드의 동작을 변경하지 않도록 보장할 것입니다. 그러니 테스트를 작성하고 TDD를 사용하는 것도 잊지 마세요.

추천 학습: "PHP 비디오 튜토리얼"

위 내용은 PHP 코드를 더 잘 리팩토링하는 방법을 가르쳐주세요.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 learnku.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제