PHP 代码简洁之道
最后更新于:2022-04-02 02:19:25
[TOC]
> [参考网址]( https://laravel-china.org/topics/7774/the-conciseness-of-the-php-code-php-clean-code)
> PHP版本>7.1.*
## 变量
### 正则
一般:
```
$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']);
```
### 避免嵌套太深
写执行报错
不好:
```
function fibonacci(int $n)
{
if ($n < 50) {
if ($n !== 0) {
if ($n !== 1) {
return fibonacci($n - 1) + fibonacci($n - 2);
} else {
return 1;
}
} else {
return 0;
}
} else {
return 'Not supported';
}
}
```
很棒:
```
function fibonacci(int $n): int
{
if ($n === 0 || $n === 1) {
return $n;
}
if ($n > 50) {
throw new \Exception('Not supported');
}
return fibonacci($n - 1) + fibonacci($n - 2);
}
```
### 不要增加不需要的上下文
小坏坏:
```
class Car
{
public $carMake;
public $carModel;
public $carColor;
//...
}
```
好的方式:
```
class Car
{
public $make;
public $model;
public $color;
//...
}
```
### 使用默认参数而不是使用短路运算或者是条件判断
不好的做法:
**这是不太好的因为 $breweryName 可以是 NULL.**
```
function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void
{
// ...
}
```
好的做法:
类型提示 而且可以**保证 $breweryName 不会为空 NULL.**
```
function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void
{
// ...
}
```
## 函数
### 函数参数(2 个或更少)
不友好的:
```
function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void
{
// ...
}
```
友好的:
把多个参数变为 对象传入
```
//----------*创建对象*-------//
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): void
{
// ...
}
```
### 函数应该只做一件事情
不好的:
```
function emailClients(array $clients): void
{
foreach ($clients as $client) {
$clientRecord = $db->find($client);
if ($clientRecord->isActive()) {
email($client);
}
}
}
```
好的:
```
function emailClients(array $clients): void
{
$activeClients = activeClients($clients);
array_walk($activeClients, 'email');
}
function activeClients(array $clients): array
{
return array_filter($clients, 'isClientActive');
}
function isClientActive(int $client): bool
{
$clientRecord = $db->find($client);
return $clientRecord->isActive();
}
```
### 不要使用单例模式
单例模式是个 反模式。 以下转述 Brian Button 的观点:
1. 单例模式常用于 全局实例, 这么做为什么不好呢? 因为在你的代码里 你隐藏了应用的依赖关系,而没有通过接口公开依赖关系 。避免全局的东西扩散使用是一种代码味道.
3. 单例模式违反了 单一责任原则: 依据的事实就是 单例模式自己控制自身的创建和生命周期.
4. 单例模式天生就导致代码紧 耦合。这使得在许多情况下用伪造的数据 难于测试。
5. 单例模式的状态会留存于应用的整个生命周期。 这会对测试产生第二次打击,你只能让被严令需要测试的代码运行不了收场,根本**不能进行单元测试**。为何?因为每一个单元测试应该彼此独立。
### 封装条件语句
不友好的:
```
if ($article->state === 'published') {
// ...
}
```
友好的:
```
if ($article->isPublished()) {
// ...
}
```
### 避免使用条件语句
不好的:
```
class Airplane
{
// ...
public function getCruisingAltitude(): int
{
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(): int;
}
class Boeing777 implements Airplane
{
// ...
public function getCruisingAltitude(): int
{
return $this->getMaxAltitude() - $this->getPassengerCount();
}
}
class AirForceOne implements Airplane
{
// ...
public function getCruisingAltitude(): int
{
return $this->getMaxAltitude();
}
}
class Cessna implements Airplane
{
// ...
public function getCruisingAltitude(): int
{
return $this->getMaxAltitude() - $this->getFuelExpenditure();
}
}
```
## 对象和数据结构
### 使用对象封装
使用对象封装,而不是直接对属性值进行操作
不好的:
```
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 withdraw(int $amount): void
{
if ($amount > $this->balance) {
throw new \Exception('Amount greater than available balance.');
}
$this->balance -= $amount;
}
public function deposit(int $amount): void
{
$this->balance += $amount;
}
public function getBalance(): int
{
return $this->balance;
}
}
$bankAccount = new BankAccount();
// Buy shoes...
$bankAccount->withdraw($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);
}
}
```
### 避免流式接口
既避免链式调用,
如tp中
`D("dmeo")->where([])->find();`
### 别写重复代码 (DRY)
';