php代码清洁之道

yangweijie版本clean-code-php翻译并同步大量原文内容。

原文更新频率较高,我的翻译方法是直接用文本比较工具逐行对比。优先保证文字内容是最新的,再逐步提升翻译质量。

阅读过程中如果遇到各种链接失效、内容老旧、术语使用错误和其他翻译错误等问题,欢迎大家积极提交PR。

虽然很多开发者还在使用PHP5,但是本文中的大部分示例的运行环境需要PHP 7.1+。

  1. 介绍
  2. 变量
  3. 函数
  4. 对象和数据结构 Objects and Data Structures
  5. 类的SOLID原则 SOLID
  6. 别写重复代码 (DRY)
  7. 翻译

Clean Code 书中的软件工程师的原则 ,适用于PHP。 这不是风格指南。 这是一个关于开发可读、可复用并且可重构的PHP软件指南。

并不是这里所有的原则都得遵循,甚至很少的能被普遍接受。 这些虽然只是指导,但是都是Clean Code作者多年总结出来的。

本文受到 clean-code-javascript 的启发

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

type hinting and be sure that the $breweryName will not be NULL.

function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void
{
    // ...
}复制代码

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

反模式. Paraphrased from Brian Button:

  1. They are generally used as a global instance, why is that so bad? Because you hide the dependencies of your application in your code, instead of exposing them through the interfaces.
    Making something global to avoid passing it around is a code smell.
  2. They violate the single responsibility principle: by virtue of the fact that they control their own creation and lifecycle.
  3. They inherently cause code to be tightly coupled. This makes faking them out under test rather difficult in many cases.
  4. They carry state around for the lifetime of the application. Another hit to testing since you can end up with a situation where tests need to be ordered which is a big no for unit tests. Why? Because
    each unit test should be independent from the other.

There is also very good thoughts by Misko Hevery about the root of problem.

坏:

class DBConnection
{
    private static $instance;

    private function __construct(string $dsn)
    {
        // ...
    }

    public static function getInstance(): DBConnection
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    // ...
}

$singleton = DBConnection::getInstance();复制代码

好:

class DBConnection
{
    public function __construct(string $dsn)
    {
        // ...
    }

     // ...
}复制代码

Create instance of DBConnection class and configure it with DSN.

$connection = new DBConnection($dsn);复制代码

And now you must use instance of DBConnection in your application.

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

类型声明或者严格模式。 提供了基于标准PHP语法的静态类型。 手动检查类型的问题是做好了需要好多的废话,好像为了安全就可以不顾损失可读性。
保持你的PHP 整洁,写好测试,做好代码回顾。做不到就用PHP严格类型声明和严格模式来确保安全。

坏:

function combine($val1, $val2): int
{
    if (!is_numeric($val1) || !is_numeric($val2)) {
        throw new \Exception('Must be of type Number');
    }

    return $val1 + $val2;
}复制代码

好:

function combine(int $val1, int $val2): int
{
    return $val1 + $val2;
}复制代码

⬆ 返回顶部

⬆ 返回顶部

开闭原则

糟糕:

class BankAccount
{
    public $balance = 1000;
}

$bankAccount = new BankAccount();

// Buy shoes...
$bankAccount->balance -= 100;复制代码

好:

class BankAccount
{
    private $balance;

    public function __construct(int $balance = 1000)
    {
      $this->balance = $balance;
    }

    public function withdrawBalance(int $amount): void
    {
        if ($amount > $this->balance) {
            throw new \Exception('Amount greater than available balance.');
        }

        $this->balance -= $amount;
    }

    public function depositBalance(int $amount): void
    {
        $this->balance += $amount;
    }

    public function getBalance(): int
    {
        return $this->balance;
    }
}

$bankAccount = new BankAccount();

// Buy shoes...
$bankAccount->withdrawBalance($shoesPrice);

// Get balance
$balance = $bankAccount->getBalance();复制代码

⬆ 返回顶部

⬆ 返回顶部

设计模式之前所说, 我们应该尽量优先选择组合而不是继承的方式。使用继承和组合都有很多好处。 这个准则的主要意义在于当你本能的使用继承时,试着思考一下组合是否能更好对你的需求建模。 在一些情况下,是这样的。

接下来你或许会想,“那我应该在什么时候使用继承?” 答案依赖于你的问题,当然下面有一些何时继承比组合更好的说明:

  1. 你的继承表达了“是一个”而不是“有一个”的关系(人类-》动物,用户-》用户详情)
  2. 你可以复用基类的代码(人类可以像动物一样移动)
  3. 你想通过修改基类对所有派生类做全局的修改(当动物移动时,修改她们的能量消耗)

糟糕的:

class Employee 
{
    private $name;
    private $email;

    public function __construct(string $name, string $email)
    {
        $this->name = $name;
        $this->email = $email;
    }

    // ...
}


// 不好,因为Employees "有" taxdata
// 而EmployeeTaxData不是Employee类型的


class EmployeeTaxData extends Employee 
{
    private $ssn;
    private $salary;
    
    public function __construct(string $name, string $email, string $ssn, string $salary)
    {
        parent::__construct($name, $email);

        $this->ssn = $ssn;
        $this->salary = $salary;
    }

    // ...
}复制代码

好:

class EmployeeTaxData 
{
    private $ssn;
    private $salary;

    public function __construct(string $ssn, string $salary)
    {
        $this->ssn = $ssn;
        $this->salary = $salary;
    }

    // ...
}

class Employee 
{
    private $name;
    private $email;
    private $taxData;

    public function __construct(string $name, string $email)
    {
        $this->name = $name;
        $this->email = $email;
    }

    public function setTaxData(string $ssn, string $salary)
    {
        $this->taxData = new EmployeeTaxData($ssn, $salary);
    }

    // ...
}复制代码

⬆ 返回顶部

连贯接口Fluent interface是一种 旨在提高面向对象编程时代码可读性的API设计模式,他基于方法链Method chaining

While there can be some contexts, frequently builder objects, where this pattern reduces the verbosity of the code (for example the PHPUnit Mock Builder or the Doctrine Query Builder), more often it comes at some costs:

  1. Breaks Encapsulation
  2. Breaks Decorators
  3. Is harder to mock in a test suite
  4. Makes diffs of commits harder to read

For more informations you can read the full blog post on this topic written by Marco Pivetta.

坏:

class Car
{
    private $make = 'Honda';
    private $model = 'Accord';
    private $color = 'white';

    public function setMake(string $make): self
    {
        $this->make = $make;

        // NOTE: Returning this for chaining
        return $this;
    }

    public function setModel(string $model): self
    {
        $this->model = $model;

        // NOTE: Returning this for chaining
        return $this;
    }

    public function setColor(string $color): self
    {
        $this->color = $color;

        // NOTE: Returning this for chaining
        return $this;
    }

    public function dump(): void
    {
        var_dump($this->make, $this->model, $this->color);
    }
}

$car = (new Car())
  ->setColor('pink')
  ->setMake('Ford')
  ->setModel('F-150')
  ->dump();复制代码

好:

class Car
{
    private $make = 'Honda';
    private $model = 'Accord';
    private $color = 'white';

    public function setMake(string $make): void
    {
        $this->make = $make;
    }

    public function setModel(string $model): void
    {
        $this->model = $model;
    }

    public function setColor(string $color): void
    {
        $this->color = $color;
    }

    public function dump(): void
    {
        var_dump($this->make, $this->model, $this->color);
    }
}

$car = new Car();
$car->setColor('pink');
$car->setMake('Ford');
$car->setModel('F-150');
$car->dump();复制代码

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

⬆ 返回顶部

DRY 原则.

尽你最大的努力去避免复制代码,它是一种非常糟糕的行为,复制代码 通常意味着当你需要变更一些逻辑时,你需要修改不止一处。

试想一下,如果你在经营一家餐厅并且你在记录你仓库的进销记录:所有 的土豆,洋葱,大蒜,辣椒等。如果你有多个列表来管理进销记录,当你 用其中一些土豆做菜时你需要更新所有的列表。如果你只有一个列表的话 只有一个地方需要更新。

通常情况下你复制代码是应该有两个或者多个略微不同的逻辑,它们大多数 都是一样的,但是由于它们的区别致使你必须有两个或者多个隔离的但大部 分相同的方法,移除重复的代码意味着用一个function/module/class创 建一个能处理差异的抽象。

正确的抽象是非常关键的,这正是为什么你必须学习遵守在Classes章节展开 的SOLID原则,不合理的抽象比复制代码更糟糕,所有务必谨慎!说到这么多, 如果你能设计一个合理的抽象,实现它!不要重复,否则你会发现任何时候当你 想修改一个逻辑时你必须修改多个地方。

坏:

function showDeveloperList(array $developers): void
{
    foreach ($developers as $developer) {
        $expectedSalary = $developer->calculateExpectedSalary();
        $experience = $developer->getExperience();
        $githubLink = $developer->getGithubLink();
        $data = [
            $expectedSalary,
            $experience,
            $githubLink
        ];

        render($data);
    }
}

function showManagerList(array $managers): void
{
    foreach ($managers as $manager) {
        $expectedSalary = $manager->calculateExpectedSalary();
        $experience = $manager->getExperience();
        $githubLink = $manager->getGithubLink();
        $data = [
            $expectedSalary,
            $experience,
            $githubLink
        ];

        render($data);
    }
}复制代码

好:

function showList(array $employees): void
{
    foreach ($employees as $employee) {
        $expectedSalary = $employee->calculateExpectedSalary();
        $experience = $employee->getExperience();
        $githubLink = $employee->getGithubLink();
        $data = [
            $expectedSalary,
            $experience,
            $githubLink
        ];

        render($data);
    }
}复制代码

极好:

It is better to use a compact version of the code.

function showList(array $employees): void
{
    foreach ($employees as $employee) {
        render([
            $employee->calculateExpectedSalary(),
            $employee->getExperience(),
            $employee->getGithubLink()
        ]);
    }
}复制代码

⬆ 返回顶部

⬆ 返回顶部