


Programming Guide for PHP Developers Part One: Reducing Complexity, Developer Programming Guide
PHP is a programming language with a high degree of freedom. It is a dynamic language and has great tolerance for programmers. As a PHP programmer, you need to understand a lot of specifications to make your code more efficient. Over the years, I have read many programming books and discussed coding style issues with many senior programmers. I’m sure I won’t remember which rule comes from which book or person, but this article (and another to follow) expresses my view on how to write better code: it can stand up to the test. The code is usually very readable and understandable. With such code, others can find problems more easily and reuse the code more easily.
Reduce the complexity of the function body
In the method or function body, reduce complexity as much as possible. The relatively low complexity makes it easier for others to read the code. In addition, doing so can also reduce the possibility of code problems, make it easier to modify, and fix problems if they occur.
Reduce the number of parentheses in functions
Use if, elseif, else and switch statements as little as possible. They add more brackets. This makes the code more understandable and harder to test (because each bracket needs to be covered by a test case). There are always ways to avoid this problem.
Agent decision-making ("Tell, don't ask")
Sometimes the if statement can be moved to another object to make it clearer. For example:
if($a->somethingIsTrue()) { $a->doSomething(); }
can be changed to:
Here, the specific judgment is done by the doSomething() method of the $a object. We don't need to think any more about this, we just need to call doSomething() safely. This approach elegantly follows the command-don’t-query principle. I suggest you take a closer look at this principle, which can be applied whenever you query an object for information and make judgments based on that information.
Use map
Sometimes map statements can be used to reduce the use of if, elseif or else, for example:
can be simplified to:
Using map in this way also allows your code to follow the principle of opening for expansion and closing for modification.
if($type==='json') { return $jsonDecoder->decode($body); }elseif($type==='xml') { return $xmlDecoder->decode($body); }else{ throw new \LogicException( 'Type "'.$type.'" is not supported' ); }Forced type
$decoders= ...;// a map of type (string) to corresponding Decoder objects if(!isset($decoders[$type])) { thrownew\LogicException( 'Type "'.$type.'" is not supported' ); }
Can be simplified by forcing $a to use type A:
Of course, we can support the "null" case in other ways. This will be mentioned in a later article.
if($a instanceof A) { // happy path return $a->someInformation(); }elseif($a=== null) { // alternative path return 'default information'; }Return early
return $a->someInformation();
The if statement here is not a branch of function execution, it is just a check of a precondition. Sometimes we can let PHP do the precondition checking itself (such as using appropriate type hints). However, PHP cannot complete all precondition checks, so you still need to keep some in the code. In order to reduce complexity, we need to return as early as possible when we know in advance that the code will go wrong, when the input is wrong, and when the result is already known.
The effect of returning as early as possible is that the subsequent code does not need to be indented as before:
if(!$a instanceof A) { throw new \InvalidArgumentException(...); } // happy path return $a->someInformation();
Create small logical units
如果函数体过长,就很难理解这个函数到底在干什么。跟踪变量的使用、变量类型、变量声明周期、调用的辅助函数等等,这些都会消耗很多脑细胞。如果函数比较小,对于理解函数功能很有帮助(例如,函数只是接受一些输入,做一些处理,再返回结果)。
使用辅助函数
在使用之前的原则减少括号之后,你还可以通过把函数拆分成更小的逻辑单元做到让函数更清晰。你可以把实现一个子任务的代码行看做一组代码,这些代码组直接用空行来分隔。然后考虑如何把它们拆分成辅助方法(即重构中的提炼方法)。
辅助方法一般是 private 的方法,只会被所属的特定类的对象调用。通常它们不需要访问实例的变量,这种情况需要定义为 static 的方法。在我的经验中,private (static)的辅助方法通常会汇总到分离的类中,并且定义成 public (static 或 instance)的方法,至少在测试驱动开发的时候使用一个协作类就是这种情形。
减少临时变量
长的函数通常需要一些变量来保存中间结果。这些临时变量跟踪起来比较麻烦:你需要记住它们是否已经初始化了,是否还有用,现在的值又是多少等等。
上节提到的辅助函数有助于减少临时变量:
public function capitalizeAndReverse(array $names) { $capitalized = array_map('ucfirst', $names); $capitalizedAndReversed = array_map('strrev', $capitalized); return $capitalizedAndReversed; }
使用辅助方法,我们可以不用临时变量了:
public function capitalizeAndReverse(array $names) { return self::reverse( self::capitalize($names) ); } private static function reverse(array $names) { return array_map('strrev', $names); } private static function capitalize(array $names) { return array_map('ucfirst', $names); }
正如你所见,我们把函数变成新函数的组合,这样变得更易懂,也更容易修改。某种方式上,代码还有点符合“扩展开放/修改关闭”,因为我们基本上不需要再修改辅助函数。
由于很多算法需要遍历容器,从而得到新的容器或者计算出一个结果,此时把容器本身当做一个“一等公民”并且附加上相关的行为,这样做是很有意义的:
classNames { private $names; public function __construct(array $names) { $this->names = $names; } public function reverse() { return new self( array_map('strrev', $names) ); } public function capitalize() { return new self( array_map('ucfirst', $names) ); } } $result = (newNames([...]))->capitalize()->reverse();
这样做可以简化函数的组合。
虽然减少临时变量通常会带来好的设计,不过上面的例子中也没必要干掉所有的临时变量。有时候临时变量的用处是很清晰的,作用也是一目了然的,就没必要精简。
使用简单的类型
追踪变量的当前取值总是很麻烦的,当不清楚变量的类型时尤其如此。而如果一个变量的类型不是固定的,那简直就是噩梦。
数组只包含同一种类型的值
使用数组作为可遍历的容器时,不管什么情况都要确保只使用同一种类型的值。这可以降低遍历数组读取数据的循环的复杂度:
foreach($collection as $value) { // 如果指定$value的类型,就不需要做类型检查 }
你的代码编辑器也会为你提供数组值的类型提示:
/** * @param DateTime[] $collection */ public function doSomething(array $collection) { foreach($collection as $value) { // $value是DateTime类型 } }
而如果你不能确定 $value 是 DateTime 类型的话,你就不得不在函数里添加前置判断来检查其类型。beberlei/assert库可以让这个事情简单一些:
useAssert\Assertion public function doSomething(array $collection) { Assertion::allIsInstanceOf($collection, \DateTime::class); ... }
如果容器里有内容不是 DateTime 类型,这会抛出一个 InvalidArgumentException 异常。除了强制输入相同类型的值之外,使用断言(assert)也是降低代码复杂度的一种手段,因为你可以不在函数的头部去做类型的检查。
简单的返回值类型
只要函数的返回值可能有不同的类型,就会极大的增加调用端代码的复杂度:
$result= someFunction(); if($result=== false) { ... }else if(is_int($result)) { ... }
PHP 并不能阻止你返回不同类型的值(或者使用不同类型的参数)。但是这样做只会造成大量的混乱,你的程序里也会到处都充斥着 if 语句。
下面是一个经常遇到的返回混合类型的例子:
/** * @param int $id * @return User|null */ public function findById($id) { ... }
这个函数会返回 User 对象或者 null,这种做法是有问题的,如果不检查返回值是否合法的 User 对象,我们是不能去调用返回值的方法的。在 PHP 7之前,这样做会造成"Fatal error",然后程序崩溃。
下一篇文章我们会考虑 null,告诉你如何去处理它们。
可读的表达式
我们已经讨论过不少降低函数的整体复杂度的方法。在更细粒度上我们也可以做一些事情来减少代码的复杂度。
隐藏复杂的逻辑
通常可以把复杂的表达式变成辅助函数。看看下面的代码:
if(($a||$b) &&$c) { ... }
可以变得更简单一些,像这样:
if(somethingIsTheCase($a,$b,$c)) { ... }
阅读代码时可以清楚的知道这个判断依赖 $a, $b 和 $c 三个变量,而函数名也可以很好的表达判断条件的内容。
使用布尔表达式
if 表达式的内容可以转换成布尔表达式。不过 PHP 也没有强制你必须提供 boolean 值:
$a=new\DateTime(); ... if($a) { ... }
$a 会自动转换成 boolean 类型。强制类型转换是 bug 的主要来源之一,不过还有一个问题是会对代码的理解带来复杂性,因为这里的类型转换是隐式的。PHP 的隐式转换的替代方案是显式的进行类型转换,例如:
if($a instanceof DateTime) { ... }
如果你知道比较的是 bool 类型,就可以简化成这样:
if($b=== false) { ... }
使用 ! 操作符则还可以简化:
if(!$b) { ... }
不要 Yoda 风格的表达式
Yoda 风格的表达式就像这样:
if('hello'===$result) { ... }
这种表达式主要是为了避免下面的错误:
if($result='hello') { ... }
这里 'hello' 会赋值给 $result,然后成为整个表达式的值。'hello' 会自动转换成 bool 类型,这里会转换成 true。于是 if 分支里的代码在这里会总是被执行。
使用 Yoda 风格的表达式可以帮你避免这类问题:
if('hello'=$result) { ... }
我觉得实际情况下不太会有人出现这种错误,除非他还在学习 PHP 的基本语法。而且,Yoda 风格的代码也有不小的代价:可读性。这样的表达式不太易读,也不太容易懂,因为这不符合自然语言的习惯。
以上就是本文的全部内容,希望对大家的学习有所帮助。
您可能感兴趣的文章:
- php和js编程中的延迟执行效果的代码
- PHP编程过程中需要了解的this,self,parent的区别
- 《PHP编程最快明白》第四讲:日期、表单接收、session、cookie
- 《PHP编程最快明白》第六讲:Mysql数据库操作
- php学习笔记之面向对象编程
- 深入php之规范编程命名小结
- PHP编程风格规范分享
- php使用socket编程示例
- PHP多进程编程实例

php把负数转为正整数的方法:1、使用abs()函数将负数转为正数,使用intval()函数对正数取整,转为正整数,语法“intval(abs($number))”;2、利用“~”位运算符将负数取反加一,语法“~$number + 1”。

实现方法:1、使用“sleep(延迟秒数)”语句,可延迟执行函数若干秒;2、使用“time_nanosleep(延迟秒数,延迟纳秒数)”语句,可延迟执行函数若干秒和纳秒;3、使用“time_sleep_until(time()+7)”语句。

php字符串有下标。在PHP中,下标不仅可以应用于数组和对象,还可应用于字符串,利用字符串的下标和中括号“[]”可以访问指定索引位置的字符,并对该字符进行读写,语法“字符串名[下标值]”;字符串的下标值(索引值)只能是整数类型,起始值为0。

php除以100保留两位小数的方法:1、利用“/”运算符进行除法运算,语法“数值 / 100”;2、使用“number_format(除法结果, 2)”或“sprintf("%.2f",除法结果)”语句进行四舍五入的处理值,并保留两位小数。

判断方法:1、使用“strtotime("年-月-日")”语句将给定的年月日转换为时间戳格式;2、用“date("z",时间戳)+1”语句计算指定时间戳是一年的第几天。date()返回的天数是从0开始计算的,因此真实天数需要在此基础上加1。

在php中,可以使用substr()函数来读取字符串后几个字符,只需要将该函数的第二个参数设置为负值,第三个参数省略即可;语法为“substr(字符串,-n)”,表示读取从字符串结尾处向前数第n个字符开始,直到字符串结尾的全部字符。

方法:1、用“str_replace(" ","其他字符",$str)”语句,可将nbsp符替换为其他字符;2、用“preg_replace("/(\s|\ \;||\xc2\xa0)/","其他字符",$str)”语句。

查找方法:1、用strpos(),语法“strpos("字符串值","查找子串")+1”;2、用stripos(),语法“strpos("字符串值","查找子串")+1”。因为字符串是从0开始计数的,因此两个函数获取的位置需要进行加1处理。


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

EditPlus Chinese cracked version
Small size, syntax highlighting, does not support code prompt function

ZendStudio 13.5.1 Mac
Powerful PHP integrated development environment

Safe Exam Browser
Safe Exam Browser is a secure browser environment for taking online exams securely. This software turns any computer into a secure workstation. It controls access to any utility and prevents students from using unauthorized resources.

Dreamweaver Mac version
Visual web development tools

VSCode Windows 64-bit Download
A free and powerful IDE editor launched by Microsoft
