Home > Backend Development > PHP Tutorial > Programming Guide for PHP Developers Part 1: Reducing Complexity, Developer Programming Guide_PHP Tutorial

Programming Guide for PHP Developers Part 1: Reducing Complexity, Developer Programming Guide_PHP Tutorial

WBOY
Release: 2016-07-12 09:00:24
Original
822 people have browsed it

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();
 }
Copy after login

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'
  );
}
Copy after login
Forced type


Many if statements can be avoided by using types more strictly, for example:
$decoders= ...;// a map of type (string) to corresponding Decoder objects
 
if(!isset($decoders[$type])) {
  thrownew\LogicException(
    'Type "'.$type.'" is not supported'
  );
}
Copy after login


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';
}
Copy after login
Return early


Many times, a branch in a function is not a real branch, but some pre- or post-conditions, like this: // Pre-conditions
return $a->someInformation();
Copy after login


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:


Like the template above, the code will be changed to be more readable and understandable.
if(!$a instanceof A) {
  throw new \InvalidArgumentException(...);
}
 
// happy path
return $a->someInformation();
Copy after login

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;
}
Copy after login

使用辅助方法,我们可以不用临时变量了:

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);
}
Copy after login

正如你所见,我们把函数变成新函数的组合,这样变得更易懂,也更容易修改。某种方式上,代码还有点符合“扩展开放/修改关闭”,因为我们基本上不需要再修改辅助函数。
由于很多算法需要遍历容器,从而得到新的容器或者计算出一个结果,此时把容器本身当做一个“一等公民”并且附加上相关的行为,这样做是很有意义的:

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();
Copy after login

这样做可以简化函数的组合。
虽然减少临时变量通常会带来好的设计,不过上面的例子中也没必要干掉所有的临时变量。有时候临时变量的用处是很清晰的,作用也是一目了然的,就没必要精简。

使用简单的类型

追踪变量的当前取值总是很麻烦的,当不清楚变量的类型时尤其如此。而如果一个变量的类型不是固定的,那简直就是噩梦。
数组只包含同一种类型的值
使用数组作为可遍历的容器时,不管什么情况都要确保只使用同一种类型的值。这可以降低遍历数组读取数据的循环的复杂度:

foreach($collection as $value) {
  // 如果指定$value的类型,就不需要做类型检查
}
Copy after login

你的代码编辑器也会为你提供数组值的类型提示:

/**
 * @param DateTime[] $collection
 */
public function doSomething(array $collection) {
  foreach($collection as $value) {
    // $value是DateTime类型
  }
}
Copy after login

而如果你不能确定 $value 是 DateTime 类型的话,你就不得不在函数里添加前置判断来检查其类型。beberlei/assert库可以让这个事情简单一些:

useAssert\Assertion
 
public function doSomething(array $collection) {
  Assertion::allIsInstanceOf($collection, \DateTime::class);
 
  ...
}
Copy after login

如果容器里有内容不是 DateTime 类型,这会抛出一个 InvalidArgumentException 异常。除了强制输入相同类型的值之外,使用断言(assert)也是降低代码复杂度的一种手段,因为你可以不在函数的头部去做类型的检查。
简单的返回值类型
只要函数的返回值可能有不同的类型,就会极大的增加调用端代码的复杂度:

$result= someFunction();
if($result=== false) {
  ...
}else if(is_int($result)) {
  ...
}
Copy after login

PHP 并不能阻止你返回不同类型的值(或者使用不同类型的参数)。但是这样做只会造成大量的混乱,你的程序里也会到处都充斥着 if 语句。
下面是一个经常遇到的返回混合类型的例子:

/**
 * @param int $id
 * @return User|null
 */
public function findById($id)
{
  ...
}
Copy after login

这个函数会返回 User 对象或者 null,这种做法是有问题的,如果不检查返回值是否合法的 User 对象,我们是不能去调用返回值的方法的。在 PHP 7之前,这样做会造成"Fatal error",然后程序崩溃。
下一篇文章我们会考虑 null,告诉你如何去处理它们。
可读的表达式

我们已经讨论过不少降低函数的整体复杂度的方法。在更细粒度上我们也可以做一些事情来减少代码的复杂度。
隐藏复杂的逻辑

通常可以把复杂的表达式变成辅助函数。看看下面的代码:

if(($a||$b) &&$c) {
  ...
}
Copy after login

可以变得更简单一些,像这样:

if(somethingIsTheCase($a,$b,$c)) {
  ...
}
Copy after login

阅读代码时可以清楚的知道这个判断依赖 $a, $b 和 $c 三个变量,而函数名也可以很好的表达判断条件的内容。
使用布尔表达式
if 表达式的内容可以转换成布尔表达式。不过 PHP 也没有强制你必须提供 boolean 值:

$a=new\DateTime();
...
 
if($a) {
  ...
}
Copy after login

$a 会自动转换成 boolean 类型。强制类型转换是 bug 的主要来源之一,不过还有一个问题是会对代码的理解带来复杂性,因为这里的类型转换是隐式的。PHP 的隐式转换的替代方案是显式的进行类型转换,例如:

if($a instanceof DateTime) {
  ...
}
Copy after login

如果你知道比较的是 bool 类型,就可以简化成这样:

if($b=== false) {
  ...
}
Copy after login

使用 ! 操作符则还可以简化:

if(!$b) {
  ...
}
Copy after login

不要 Yoda 风格的表达式
Yoda 风格的表达式就像这样:

if('hello'===$result) {
  ...
}
Copy after login

这种表达式主要是为了避免下面的错误:

if($result='hello') {
  ...
}
Copy after login

这里 'hello' 会赋值给 $result,然后成为整个表达式的值。'hello' 会自动转换成 bool 类型,这里会转换成 true。于是 if 分支里的代码在这里会总是被执行。
使用 Yoda 风格的表达式可以帮你避免这类问题:

if('hello'=$result) {
  ...
}
Copy after login

我觉得实际情况下不太会有人出现这种错误,除非他还在学习 PHP 的基本语法。而且,Yoda 风格的代码也有不小的代价:可读性。这样的表达式不太易读,也不太容易懂,因为这不符合自然语言的习惯。

以上就是本文的全部内容,希望对大家的学习有所帮助。

您可能感兴趣的文章:

  • php和js编程中的延迟执行效果的代码
  • PHP编程过程中需要了解的this,self,parent的区别
  • 《PHP编程最快明白》第四讲:日期、表单接收、session、cookie
  • 《PHP编程最快明白》第六讲:Mysql数据库操作
  • php学习笔记之面向对象编程
  • 深入php之规范编程命名小结
  • PHP编程风格规范分享
  • php使用socket编程示例
  • PHP多进程编程实例

www.bkjia.comtruehttp://www.bkjia.com/PHPjc/1093710.htmlTechArticle给PHP开发者的编程指南 第一部分降低复杂程度,开发者编程指南 PHP 是一门自由度很高的编程语言。它是动态语言,对程序员有很大的宽容...
Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template