博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
PHP 代码简洁之道
阅读量:4294 次
发布时间:2019-05-27

本文共 16813 字,大约阅读时间需要 56 分钟。

一、

PHP 简洁代码之道 , 是基于 Clean Code: A Handbook of Agile Software Craftmanship(Clean Code: 敏捷软件开发工艺手册) 这本书做的指南,该书是 写的关于如何编写可维护代码的经典书籍。

clean-code-php 指南的灵感来源于 Javascript 版本的 ,在其基础加上了 PHP 的特点。

以下是我最喜欢的 clean-code-php 仓库中的一些点:

不添加不必要的上下文

不好的:

好的:

函数参数(不要超过2个)

不好的:

好的:

title = 'Foo';$config->body = 'Bar';$config->buttonText = 'Baz';$config->cancellable = true;function createMenu(MenuConfig $config) { // ...}

一个函数只做一件事情

不好的:

find($client); if ($clientRecord->isActive()) { email($client); } }}

好的:

function emailClients($clients) {    $activeClients = activeClients($clients);    array_walk($activeClients, 'email');}function activeClients($clients) {    return array_filter($clients, 'isClientActive');}function isClientActive($client) {    $clientRecord = $db->find($client);    return $clientRecord->isActive();}

观点

项目作者概述了本指南的目的如下:

这里的原则并不是所有的都必须遵守,而且几乎只有少部分是被广泛认同的。它们只是一些指导方针,而不是其他,但是它们都是 Clean Code的作者集多年编纂而成的。

在一门动态语言(像 PHP或其他任何语言)领域中,某些开发者可能会不同意其中的某些(或者很多)概念和观点,但是我需要指出的是即使你对其中一些观点不认同,也不要将它们全盘否定掉。

二、变量部分

使用有意义的并且可以读出来的变量名称

不好的:

$ymdstr = $moment->format('y-m-d');

好的:

$currentDate = $moment->format('y-m-d');

对于同一类型的变量使用相同的词汇

不好的:

getUserInfo();getUserData();getUserRecord();getUserProfile();

好的:

getUser();

(译者注:都是要取用户信息,不好的案例中为同一件事起了多个名字,在编码中是要避免的)

使用易于查找的名称(第一部分)

我们读代码的时候要比写代码的时候多的多,所以我们写的代码易读易查找是很重要的。如果不命名好对理解我们的程序有意义的变量,我们会伤害到读我们代码的人。确保你的变量易于查找。

不好的:

// 448 是什么鬼?$result = $serializer->serialize($data, 448);

好的:

$json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

使用易查找的名称(第二部分)

不好的:

// 4 是什么鬼?if ($user->access & 4) {    // ...}

好的:

class User{    const ACCESS_READ = 1;    const ACCESS_CREATE = 2;    const ACCESS_UPDATE = 4;    const ACCESS_DELETE = 8;}if ($user->access & User::ACCESS_UPDATE) {    // do edit ...}

使用可以解释的变量

不好的:

$address = 'One Infinite Loop, Cupertino 95014';$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/';preg_match($cityZipCodeRegex, $address, $matches);saveCityZipCode($matches[1], $matches[2]);

好一点的:

这个好了一点,但是我们还是非常依赖正则

$address = 'One Infinite Loop, Cupertino 95014';$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/';preg_match($cityZipCodeRegex, $address, $matches);list(, $city, $zipCode) = $matches;saveCityZipCode($city, $zipCode);

好的:

通过使用命名子模式我们不必再依赖正则

$address = 'One Infinite Loop, Cupertino 95014';$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(?
.+?)\s*(?
\d{5})?$/';preg_match($cityZipCodeRegex, $address, $matches);saveCityZipCode($matches['city'], $matches['zipCode']);

不要让读者猜

不要强迫你代码的读者去翻译变量的含义,显式比隐式要好

不好的:

$l = ['Austin', 'New York', 'San Francisco'];for ($i = 0; $i < count($l); $i++) {    $li = $l[$i];    doStuff();    doSomeOtherStuff();    // ...    // ...    // ...    // $li 变量代表什么???    dispatch($li);}

好的:

$locations = ['Austin', 'New York', 'San Francisco'];foreach ($locations as $location) {    doStuff();    doSomeOtherStuff();    // ...    // ...    // ...    dispatch($location);}

不增加不必要的语境

如果你的类名或者对象名已经告诉了你一些信息,就不要在方法和和属性上重复他们。

不好的:

class Car{    public $carMake;    public $carModel;    public $carColor;    //...}

好的:

class Car{    public $make;    public $model;    public $color;    //...}

使用默认参数

不太好的:

这样不太好,因为 $breweryName 可能被传入 NULL

function createMicrobrewery($breweryName = 'Hipster Brew Co.'){    // ...}

好一点的:

这个比上一个版本好一点,因为可以保证 $breweryName 不为 Null

function createMicrobrewery($name = null){    $breweryName = $name ?: 'Hipster Brew Co.';    // ...}

好的:

如果你只需要支持 PHP 7以上的版本, 你可以使用使用类型提示来保证 $breweryName 不为 NULL

function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'){    // ...}

三、

1. 函数参数(不要超过两个)

限制函数的参数数量是非常重要的,因为它使你的函数更容易测试。超过三个参数会导致参数之间的组合过多,你必须对每个单独的参数测试大量不同的情况。

没有参数最理想的情况,一个或两个参数是可以接受的,三个以上是应该避免的。这是很重要的。通常,如果你有两个以上的参数,那么你的函数可能试图做的太多,如果不是,你可能需要将一个高级别的对象传当做参数传进去。

不好的

function createMenu($title, $body, $buttonText, $cancellable){    // ...}

好的:

class MenuConfig{    public $title;    public $body;    public $buttonText;    public $cancellable = false;}$config = new MenuConfig();$config->title = 'Foo';$config->body = 'Bar';$config->buttonText = 'Baz';$config->cancellable = true;function createMenu(MenuConfig $config){    // ...}

2. 一个函数只做一件事

这是软件工程中最重要的原则。

不好的:

function emailClients($clients){    foreach ($clients as $client) {        $clientRecord = $db->find($client);        if ($clientRecord->isActive()) {            email($client);        }    }}

不好的

function emailClients($clients){    $activeClients = activeClients($clients);    array_walk($activeClients, 'email');}function activeClients($clients){    return array_filter($clients, 'isClientActive');}function isClientActive($client){    $clientRecord = $db->find($client);    return $clientRecord->isActive();}

3. 函数名要能说明它是做什么的

不好的:

class Email{    //...    public function handle()    {        mail($this->to, $this->subject, $this->body);    }}$message = new Email(...);// 这是什么?一条消息的句柄? 你是要写一个文件么?(读者的疑问)$message->handle();

好的

class Email {    //...    public function send()    {        mail($this->to, $this->subject, $this->body);    }}$message = new Email(...);//  一目了然$message->send();

4. 函数应该只做一层抽象

当你有多个层次的抽象时,你的函数通常都在试图做的太多。拆分这些函数,可以让代码可重用性更高且更易测试。

不好的:

function parseBetterJSAlternative($code){    $regexes = [        // ...    ];    $statements = explode(' ', $code);    $tokens = [];    foreach ($regexes as $regex) {        foreach ($statements as $statement) {            // ...        }    }    $ast = [];    foreach ($tokens as $token) {        // lex...    }    foreach ($ast as $node) {        // parse...    }}

同样不好的:

我们以前从函数中迁出去了一些工作,但是 parseBetterJSAlternative() 函数还是很复杂,不可测试。

function tokenize($code){    $regexes = [        // ...    ];    $statements = explode(' ', $code);    $tokens = [];    foreach ($regexes as $regex) {        foreach ($statements as $statement) {            $tokens[] = /* ... */;        }    }    return $tokens;}function lexer($tokens){    $ast = [];    foreach ($tokens as $token) {        $ast[] = /* ... */;    }    return $ast;}function parseBetterJSAlternative($code){    $tokens = tokenize($code);    $ast = lexer($tokens);    foreach ($ast as $node) {        // parse...    }}

好的:

最好的解决方案是移除 parseBetterJSAlternative 函数的依赖

class Tokenizer{    public function tokenize($code)    {        $regexes = [            // ...        ];        $statements = explode(' ', $code);        $tokens = [];        foreach ($regexes as $regex) {            foreach ($statements as $statement) {                $tokens[] = /* ... */;            }        }        return $tokens;    }}class Lexer{    public function lexify($tokens)    {        $ast = [];        foreach ($tokens as $token) {            $ast[] = /* ... */;        }        return $ast;    }}class BetterJSAlternative{    private $tokenizer;    private $lexer;    public function __construct(Tokenizer $tokenizer, Lexer $lexer)    {        $this->tokenizer = $tokenizer;        $this->lexer = $lexer;    }    public function parse($code)    {        $tokens = $this->tokenizer->tokenize($code);        $ast = $this->lexer->lexify($tokens);        foreach ($ast as $node) {            // parse...        }    }}

5.不要使用标志做函数参数

标志相当于告诉使用者,这个函数不止做一件事。函数应该只做一件事,如果函数根据布尔值执行不同代码路径,则你应该将函数拆分。

不好的:

function createFile($name, $temp = false){    if ($temp) {        touch('./temp/'.$name);    } else {        touch($name);    }}

好的:

function createFile($name){    touch($name);}function createTempFile($name){    touch('./temp/'.$name);}

6.避免副作用

如果一个函数做了“拿到一个值并返回一个值或者多个值”以外的事情,那么这个函数就有可能产生副作用,副作用可能是意外的写入了文件、修改了全局变量、或者打钱给了陌生人。

现在假如你确实要在函数中做一些有可能产生副作用的事情。 比如要写一个文件,你需要做的是将写文件的操作集中到一处,而不是在几个函数或者类里对同一个文件做操作,实现一个服务(函数或者类)去操作它,有且仅有一个。

关键是要能避免常见的陷阱:像是在没有结构的对象之间共享状态、使用可能被写入任何值的可变数据类型、 不集中处理有可能产生副作用的操作。 如果你能做到这些,你会比绝大多数程序员更快乐。

不好的:

// Global variable referenced by following function.// If we had another function that used this name, now it'd be an array and it could break it.$name = 'Ryan McDermott';function splitIntoFirstAndLastName(){    global $name;    $name = explode(' ', $name);}splitIntoFirstAndLastName();var_dump($name); // ['Ryan', 'McDermott'];

好的:

function splitIntoFirstAndLastName($name){    return explode(' ', $name);}$name = 'Ryan McDermott';$newName = splitIntoFirstAndLastName($name);var_dump($name); // 'Ryan McDermott';var_dump($newName); // ['Ryan', 'McDermott'];

7. 不要修改全局变量

在许多编程语言中污染全局是一种糟糕的做法,因为你的库可能会与另一个库冲突,但是你的库的用户却一无所知,直到在生产环境中爆发异常。让我们来考虑一个例子:如果你想要拿到配置数组怎么办?你可以编写全局函数,如config(),但是它可能与另一个试图做同样事情的库冲突。

不好的:

function config(){    return  [        'foo' => 'bar',    ]}

好的

class Configuration{    private $configuration = [];    public function __construct(array $configuration)    {        $this->configuration = $configuration;    }    public function get($key)    {        return isset($this->configuration[$key]) ? $this->configuration[$key] : null;    }}

加载配置并且创建 Configuration 类的实例

$configuration = new Configuration([    'foo' => 'bar',]);

现在你应该在你的应用程序中使用 Configuration类 的实例 。

四、

8. 不要使用单例模式

(译者注:这一条有些难理解,看不懂就略过吧)

单例模式是一种反模式,Brian Button 的解释:

  1. 单例通常被用做一个全局的实例,为什么不好?因为你在代码中隐藏了依赖,而不是通过接口暴露他们。通过将一些东西放到全局来避免传递他们是一种“代码异味(code smell)”

code smell 是指能够被开发者察觉到的不好的形式

  1. 它违反了 SPR原则(single responsibility principle): 由它自己控制自己的创建和生命周期
  2. 它本身就导致了代码的紧耦合。大多数情况下这使得通过伪造数据来测试变的相当困难。

不好的

class DBConnection{    private static $instance;    private function __construct($dsn)    {        // ...    }    public static function getInstance()    {        if (self::$instance === null) {            self::$instance = new self();        }        return self::$instance;    }    // ...}$singleton = DBConnection::getInstance();

好的:

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

创建 DBConneciton 的实例,并配置

这样你就可以在你的应用程序中使用 DBConnection 的实例了。

9 封装条件判断

不好的:

if ($article->state === 'published') {    // ...}

好的:

if ($article->isPublished()) {    // ...}

避免否定类型的判断

不好的:

function isDOMNodeNotPresent($node){    // ...}if (!isDOMNodeNotPresent($node)){    // ...}

好的

function isDOMNodePresent($node){    // ...}if (isDOMNodePresent($node)) {    // ...}

10. 避免条件判断

这似乎是一个不可能完成的任务。人们会问“如果不用 if 语句我该怎么做?”,答案是在许多情况下,你可以用来实现同样的效果。你可能还会问“这样有什么好处?”,答案是我们之前提到的原则:“一个函数应该只做一件事”, 当你的类或函数中有了 if 语句,相当于告诉别人你的函数做了一件以上的事情。

不好的:

class Airplane{    // ...    public function getCruisingAltitude()    {        switch ($this->type) {            case '777':                return $this->getMaxAltitude() - $this->getPassengerCount();            case 'Air Force One':                return $this->getMaxAltitude();            case 'Cessna':                return $this->getMaxAltitude() - $this->getFuelExpenditure();        }    }}

好的:

interface Airplane{    // ...    public function getCruisingAltitude();}class Boeing777 implements Airplane{    // ...    public function getCruisingAltitude()    {        return $this->getMaxAltitude() - $this->getPassengerCount();    }}class AirForceOne implements Airplane{    // ...    public function getCruisingAltitude()    {        return $this->getMaxAltitude();    }}class Cessna implements Airplane{    // ...    public function getCruisingAltitude()    {        return $this->getMaxAltitude() - $this->getFuelExpenditure();    }}

11. 避免类型检查(第一部分)

PHP是弱类型语言,意味着你的函数可以接收任何类型的参数。有时你会因为这点自由而受害,这时你可能会在函数中检查参数类型,有许多途径可以避免在函数能做类型检查,首先要做的是有一致的接口。

不好的

function travelToTexas($vehicle){    if ($vehicle instanceof Bicycle) {        $vehicle->peddleTo(new Location('texas'));    } elseif ($vehicle instanceof Car) {        $vehicle->driveTo(new Location('texas'));    }}

好的:

function travelToTexas(Traveler $vehicle){    $vehicle->travelTo(new Location('texas'));}

避免类型检查(第二部分)

如果现在要处理的是基础数据类型,像字符串,整型和数组。现在如果你用的 PHP7以上的版本,而且现在很明显你也不能用多态。现在你应该考虑使用或者严格模式,它使你能够在php语法层面提供静态类型检查。手动判断类型的问题是,你需要添加许多额外的代码,你获得的人造的“类型安全”抵不过你代码可读性的损失。

不好的

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

好的

function combine(int $val1, int $val2){    return $val1 + $val2;}

12.移除废弃的代码

废弃的代码跟重复的代码一样不好,没有理由在你的代码库继续保留他们,如果你想找回他们,到版本历史中找回就行了。

不好的

function oldRequestModule($url){    // ...}function newRequestModule($url){    // ...}$request = newRequestModule($requestUrl);inventoryTracker('apples', $request, 'www.inventory-awesome.io');

好的

function requestModule($url){    // ...}$request = requestModule($requestUrl);inventoryTracker('apples', $request, 'www.inventory-awesome.io');

五、

1.使用 setter 和 getter

在 PHP 中,你可以为方法设置 public, protectedprivate 关键字。使用这些关键字你可以控制一个对象的属性修改权限。

  • 如果除了获取对象属性你还想做一些别的事,就不用再到代码库中去寻找并修改每一个修改对象属性的地方(属性入口归一)。
  • 方便数据验证
  • 封装内部实现
  • 获取和设置时方便添加日志和错误处理
  • 继承了类,你可以重写默认的函数
  • 我们可以延迟加载类的属性,假设它是从服务器获取的

另外,这是开放/封闭原则的一部分,是面向对象的基本设计原则。

不好的

class BankAccount{    public $balance = 1000;}$bankAccount = new BankAccount();// Buy shoes...$bankAccount->balance -= 100;

好的

class BankAccount{    private $balance;    public function __construct($balance = 1000)    {      $this->balance = $balance;    }    public function withdrawBalance($amount)    {        if ($amount > $this->balance) {            throw new \Exception('Amount greater than available balance.');        }        $this->balance -= $amount;    }    public function depositBalance($amount)    {        $this->balance += $amount;    }    public function getBalance()    {        return $this->balance;    }}$bankAccount = new BankAccount();// Buy shoes...$bankAccount->withdrawBalance($shoesPrice);// Get balance$balance = $bankAccount->getBalance();

2.让对象有 私有(private)/受保护的(protected) 的成员

不好的

class Employee{    public $name;    public function __construct($name)    {        $this->name = $name;    }}$employee = new Employee('John Doe');echo 'Employee name: '.$employee->name; // Employee name: John Doe

好的

class Employee{    private $name;    public function __construct($name)    {        $this->name = $name;    }    public function getName()    {        return $this->name;    }}$employee = new Employee('John Doe');echo 'Employee name: '.$employee->getName(); // Employee name: John Doe

六、

使用组合而不是继承

“”在设计模式里所声明的,你应该优先选择“组合模式”而不是“继承”

译者注:Gang of Four 译成 四人帮,指代 DesignPatternBook 的四位作者

不论是使用“组合模式”还是使用“继承”都有许多理由。

这个话题的要点是当你本能的要使用继承时就想一想是否“组合模式”能帮你更好的解决问题。

你可能会问,“我什么时候应该用继承?”, 这取决于你手头的问题。这里有一个列表说明什么时候使用继承会更合适:

  1. 你的继承表达了一个“is-a(是)” 的关系,不是“has-a(有)”的关系(对比人类 -> 动物 和 用户-> 用户详情 )
  2. 你能从基础类中复用代码(人类能够像所有动物一样移动)
  3. 你想通过修改全局类来对所有派生类进行修改。(例如改变所有动物移动消耗的热量)

不好的:

class Employee {    private $name;    private $email;    public function __construct($name, $email)    {        $this->name = $name;        $this->email = $email;    }    // ...}// 不好,因为是雇员有税收数据// 税收数据不是一种雇员,所以使用继承不适合class EmployeeTaxData extends Employee {    private $ssn;    private $salary;    public function __construct($name, $email, $ssn, $salary)    {        parent::__construct($name, $email);        $this->ssn = $ssn;        $this->salary = $salary;    }    // ...}

好的:

class EmployeeTaxData {    private $ssn;    private $salary;    public function __construct($ssn, $salary)    {        $this->ssn = $ssn;        $this->salary = $salary;    }    // ...}class Employee {    private $name;    private $email;    private $taxData;    public function __construct($name, $email)    {        $this->name = $name;        $this->email = $email;    }    public function setTaxData($ssn, $salary)    {        $this->taxData = new EmployeeTaxData($ssn, $salary);    }    // ...}

避免使用流式接口(fluent interfaces)

是一种面向对象的接口,通过(链式操作)来提高代码的可读性。

然而有一些Context,通常是构建对象,这个设计模式虽然减少了代码的啰嗦(例如: 或者 Doctrine Query Builder),大部分时候他会产生如下这些消耗:

  • 破坏了Encapsulation
  • 破坏了 Decorators
  • 难以 一个测试套件
  • 使 commit 之间的变更难以阅读
    想获取更多信息请阅读,请阅读 关于这个话题的

不好的:

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

 

 

 

 

转载地址:http://uruws.baihongyu.com/

你可能感兴趣的文章
学习笔记_vnpy实战培训day06
查看>>
聚合搜索引擎
查看>>
Python super钻石继承
查看>>
回测引擎代码分析流程图
查看>>
Excel 如何制作时间轴
查看>>
股票网格交易策略
查看>>
matplotlib绘图跳过时间段的处理方案
查看>>
vnpy学习_04回测评价指标的缺陷
查看>>
ubuntu终端一次多条命令方法和区别
查看>>
python之偏函数
查看>>
vnpy学习_06回测结果可视化改进
查看>>
读书笔记_量化交易如何建立自己的算法交易01
查看>>
设计模式03_工厂
查看>>
设计模式04_抽象工厂
查看>>
设计模式05_单例
查看>>
设计模式06_原型
查看>>
设计模式07_建造者
查看>>
设计模式08_适配器
查看>>
设计模式09_代理模式
查看>>
设计模式10_桥接
查看>>