## **函数**
### 函数参数(最好少于2个)
限制函数参数的数量极其重要,这样测试你的函数容易点。有超过3个可选参数参数导致一个爆炸式组合增长,你会有成吨独立参数情形要测试。
无参数是理想情况,1个或2个都可以,最好避免3个,再多就需要作处理了。如果函数存在超过两个参数,说明它要处理的事太多了。 如果必须要传入很多的参数数据,建议封装一个高级别的对象作为参数。
**差:**
```php
function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void
{
// ...
}
```
**优:**
```php
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
{
// ...
}
```
### 函数应该只做一件事
这是迄今为止软件工程里最重要的一个规则。当一个函数做超过一件事的时候,他们就难于实现、测试和理解。当你把一个函数拆分到只剩一个功能时,他们就容易被重构,然后你的代码读起来就更清晰。如果你光遵循这条规则,你就领先于大多数开发者了。
**差:**
```php
function emailClients(array $clients): void
{
foreach ($clients as $client) {
$clientRecord = $db->find($client);
if ($clientRecord->isActive()) {
email($client);
}
}
}
```
**优:**
```php
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();
}
```
### 函数名应该是有意义的动词(或表明具体做了什么事)
**差:**
```php
class Email
{
//...
public function handle(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// 啥?handle处理一个消息干嘛了?是往一个文件里写码?
$message->handle();
```
**优:**
```php
class Email
{
//...
public function send(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// 简单明了
$message->send();
```
### 函数里应当只有一层抽象abstraction
当你抽象层次过多时时,函数处理的事情太多了。需要拆分功能来提高可重用性和易用性,以便简化测试。
(译者注:这里从示例代码看应该是指嵌套过多)
**差:**
```php
function parseBetterJSAlternative(string $code): void
{
$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()`方法还是很复杂,而且不利于测试。
```php
function tokenize(string $code): array
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}
return $tokens;
}
function lexer(array $tokens): array
{
$ast = [];
foreach ($tokens as $token) {
$ast[] = /* ... */;
}
return $ast;
}
function parseBetterJSAlternative(string $code): void
{
$tokens = tokenize($code);
$ast = lexer($tokens);
foreach ($ast as $node) {
// 解析逻辑...
}
}
```
**优:**
最好的解决方案是把 `parseBetterJSAlternative()`方法的依赖移除。
```php
class Tokenizer
{
public function tokenize(string $code): array
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}
return $tokens;
}
}
class Lexer
{
public function lexify(array $tokens): array
{
$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(string $code): void
{
$tokens = $this->tokenizer->tokenize($code);
$ast = $this->lexer->lexify($tokens);
foreach ($ast as $node) {
// 解析逻辑...
}
}
}
```
这样我们可以对依赖做mock,并测试`BetterJSAlternative::parse()`运行是否符合预期。
### 不要用flag作为函数的参数
flag就是在告诉大家,这个方法里处理很多事。前面刚说过,一个函数应当只做一件事。 把不同flag的代码拆分到多个函数里。
**差:**
```php
function createFile(string $name, bool $temp = false): void
{
if ($temp) {
touch('./temp/'.$name);
} else {
touch($name);
}
}
```
**优:**
```php
function createFile(string $name): void
{
touch($name);
}
function createTempFile(string $name): void
{
touch('./temp/'.$name);
}
```
### 避免副作用
一个函数做了比获取一个值然后返回另外一个值或值们会产生副作用。副作用可能是写入一个文件,修改某些全局变量或者偶然的把你全部的钱给了陌生人。
现在,你的确需要在一个程序或者场合里要有副作用,像之前的例子,你也许需要写一个文件。你想要做的是把你做这些的地方集中起来。不要用几个函数和类来写入一个特定的文件。用一个服务来做它,一个只有一个。
重点是避免常见陷阱比如对象间共享无结构的数据,使用可以写入任何的可变数据类型,不集中处理副作用发生的地方。如果你做了这些你就会比大多数程序员快乐。
**差:**
```php
// 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(): void
{
global $name;
$name = explode(' ', $name);
}
splitIntoFirstAndLastName();
var_dump($name); // ['Ryan', 'McDermott'];
```
**优:**
```php
function splitIntoFirstAndLastName(string $name): array
{
return explode(' ', $name);
}
$name = 'Ryan McDermott';
$newName = splitIntoFirstAndLastName($name);
var_dump($name); // 'Ryan McDermott';
var_dump($newName); // ['Ryan', 'McDermott'];
```
### 不要写全局函数
在大多数语言中污染全局变量是一个坏的实践,因为你可能和其他类库冲突
并且调用你api的人直到他们捕获异常才知道踩坑了。让我们思考一种场景:
如果你想配置一个数组,你可能会写一个全局函数`config()`,但是他可能
和试着做同样事的其他类库冲突。
**差:**
```php
function config(): array
{
return [
'foo' => 'bar',
]
}
```
**优:**
```php
class Configuration
{
private $configuration = [];
public function __construct(array $configuration)
{
$this->configuration = $configuration;
}
public function get(string $key): ?string
{
return isset($this->configuration[$key]) ? $this->configuration[$key] : null;
}
}
```
加载配置并创建 `Configuration` 类的实例
```php
$configuration = new Configuration([
'foo' => 'bar',
]);
```
现在你必须在程序中用 `Configuration` 的实例了
### 不要使用单例模式
单例是一种 [反模式](https://en.wikipedia.org/wiki/Singleton_pattern). 以下是解释: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](https://en.wikipedia.org/wiki/Code_smell).
2. 违反了[单一响应原则]()They violate the [single responsibility principle](#single-responsibility-principle-srp): by virtue of the fact that **they control their own creation and lifecycle**.
3. 导致代码强耦合They inherently cause code to be tightly [coupled](https://en.wikipedia.org/wiki/Coupling_%28computer_programming%29). 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.
**差:**
```php
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();
```
**优:**
```php
class DBConnection
{
public function __construct(string $dsn)
{
// ...
}
// ...
}
```
创建 `DBConnection` 类的实例并通过 [DSN](http://php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters) 配置.
```php
$connection = new DBConnection($dsn);
```
现在你必须在程序中 使用 `DBConnection` 的实例了
### 封装条件语句
**差:**
```php
if ($article->state === 'published') {
// ...
}
```
**优:**
```php
if ($article->isPublished()) {
// ...
}
```
### 避免用反义条件判断
**差:**
```php
function isDOMNodeNotPresent(\DOMNode $node): bool
{
// ...
}
if (!isDOMNodeNotPresent($node))
{
// ...
}
```
**优:**
```php
function isDOMNodePresent(\DOMNode $node): bool
{
// ...
}
if (isDOMNodePresent($node)) {
// ...
}
```
### 避免条件判断
这看起来像一个不可能任务。当人们第一次听到这句话是都会这么说。
"没有`if语句`我还能做啥?" 答案是你可以使用多态来实现多种场景
的相同任务。第二个问题很常见, “这么做可以,但为什么我要这么做?”
答案是前面我们学过的一个Clean Code原则:一个函数应当只做一件事。
当你有很多含有`if`语句的类和函数时,你的函数做了不止一件事。
记住,只做一件事。
**差:**
```php
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();
}
}
}
```
**优:**
```php
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();
}
}
```
### 避免类型检查 (part 1)
PHP是弱类型的,这意味着你的函数可以接收任何类型的参数。
有时候你为这自由所痛苦并且在你的函数渐渐尝试类型检查。
有很多方法去避免这么做。第一种是统一API。
**差:**
```php
function travelToTexas($vehicle): void
{
if ($vehicle instanceof Bicycle) {
$vehicle->pedalTo(new Location('texas'));
} elseif ($vehicle instanceof Car) {
$vehicle->driveTo(new Location('texas'));
}
}
```
**优:**
```php
function travelToTexas(Traveler $vehicle): void
{
$vehicle->travelTo(new Location('texas'));
}
```
### 避免类型检查 (part 2)
如果你正使用基本原始值比如字符串、整形和数组,要求版本是PHP 7+,不用多态,需要类型检测,
那你应当考虑[类型声明](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration)或者严格模式。
提供了基于标准PHP语法的静态类型。 手动检查类型的问题是做好了需要好多的废话,好像为了安全就可以不顾损失可读性。
保持你的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;
}
```
**优:**
```php
function combine(int $val1, int $val2): int
{
return $val1 + $val2;
}
```