Home  >  Article  >  Backend Development  >  Discussion on PHP code refactoring methods

Discussion on PHP code refactoring methods

不言
不言Original
2018-04-17 13:41:421331browse

This article mainly introduces the PHP code reconstruction method, and analyzes the concepts, principles, related implementation techniques and precautions of PHP code reconstruction in detail in the form of examples. Friends in need can refer to it

This article analyzes the PHP code reconstruction method through examples. Share it with everyone for your reference, as follows:

As PHP changes from a simple scripting language to a mature programming language, the complexity of the code base of a typical PHP application also increases. increase. To control the support and maintenance of these applications, we can use various testing tools to automate the process. One of these is unit testing, which allows you to directly test the correctness of the code you write. However, often legacy code bases are not suitable for this kind of testing. This article will describe refactoring strategies for PHP code that contain common issues in order to simplify the process of testing with popular unit testing tools while reducing dependencies on improving the code base.

Introduction

Looking back at the development history of PHP, we find that it has evolved from a simple dynamic script used to replace the popular CGI scripts at the time. The language becomes a full-fledged modern programming language. As the code base grows, manual testing has become an impossible task, and all changes to the code, no matter how big or small, have an impact on the entire application. These impacts may be as small as just affecting the loading of a certain page or saving a form, or they may create problems that are difficult to detect, or they may generate errors that only occur under specific conditions. Even, it may cause previously fixed issues to reappear in the application. Many testing tools have been developed to solve these problems.

One of the popular methods is so-called functional or acceptance testing, which tests the application through its typical user interactions. This is a great method for testing individual processes in an application, but the testing process can be very slow and generally fails to test whether the underlying classes and methods are working as expected. At this time, we need to use another testing method, which is unit testing. The goal of unit testing is to test the functionality of the underlying code of the application and ensure that they produce correct results after execution. Often, these "growing" web applications slowly introduce legacy code that becomes increasingly difficult to test over time, making it difficult for the development team to ensure application test coverage. This is often called "untestable code". Now let's look at how to identify untestable code in your application, and ways to fix it.

Identifying Untestable Code

#The problem areas regarding the untestability of a code base typically when writing code are not obvious. When writing PHP application code, people tend to write code following the flow of web requests, which usually results in a more process-oriented approach to application design. The rush to complete a project or quickly fix an application can prompt developers to "cut corners" in order to get coding done quickly. Previously, poorly written or confusing code could exacerbate untestability issues in an application because developers would often make the least risky fix, even if it might create subsequent support issues. These problem areas cannot be discovered through normal unit testing.

Functions that rely on global state

Global variables are convenient in PHP applications. They allow you to initialize some variables or objects in your application and then use them elsewhere in the application. However, this flexibility comes at a price, and overuse of global variables is a common problem with untestable code. We can see this happening in Listing 1.

Listing 1. Functions that depend on global state

<?php
function formatNumber($number)
{
  global $decimal_precision, $decimal_separator, $thousands_separator;
  if ( !isset($decimal_precision) ) $decimal_precision = 2;
  if ( !isset($decimal_separator) ) $decimal_separator = &#39;.&#39;;
  if ( !isset($thousands_separator) ) $thousands_separator = &#39;,&#39;;
  return number_format($number, $decimal_precision, $decimal_separator,
 $thousands_separator);
}

These global variables bring two different question. The first problem is that you need to consider all of these global variables in your tests, ensuring that they are set to valid values ​​that are acceptable to the function. The second and more serious problem is that you cannot modify the state of subsequent tests and invalidate their results, you need to ensure that the global state is reset to the state before the test was run. PHPUnit has tools that can help you back up global variables and restore their values ​​after a test has run, which can help with this problem. However, a better approach is to enable the test class to pass the values ​​of these global variables directly to methods. Listing 2 shows an example of this approach.

Listing 2. Modify this function to support overriding global variables

<?php
function formatNumber($number, $decimal_precision = null, $decimal_separator = null,
$thousands_separator = null)
{
  if ( is_null($decimal_precision) ) global $decimal_precision;
  if ( is_null($decimal_separator) ) global $decimal_separator;
  if ( is_null($thousands_separator) ) global $thousands_separator;
  if ( !isset($decimal_precision) ) $decimal_precision = 2;
  if ( !isset($decimal_separator) ) $decimal_separator = &#39;.&#39;;
  if ( !isset($thousands_separator) ) $thousands_separator = &#39;,&#39;;
  return number_format($number, $decimal_precision, $decimal_separator,
 $thousands_separator);
}

Not only does this make the code More testable, and also makes it independent of the method's global variables. This allows us to refactor the code to no longer use global variables.

Single instance that cannot be reset

单一实例指的是旨在让应用程序中一次只存在一个实例的类。它们是应用程序中用于全局对象的一种常见模式,如数据库连接和配置设置。它们通常被认为是应用程序的禁忌, 因为许多开发人员认为创建一个总是可用的对象用处不大,因此他们并不太注意这一点。这个问题主要源于单一实例的过度使用,因为它会造成大量不可扩展的所谓 god objects 的出现。但是从测试的角度看,最大的问题是它们通常是不可更改的。清单 3就是这样一个例子。

清单 3. 我们要测试的 Singleton 对象

<?php
class Singleton
{
  private static $instance;
  protected function __construct() { }
  private final function __clone() {}
  public static function getInstance()
  {
    if ( !isset(self::$instance) ) {
      self::$instance = new Singleton;
    }
    return self::$instance;
  }
}

您可以看到,当单一实例首次实例化之后,每次调用 getInstance() 方法实际上返回的都是同一个对象,它不会创建新的对象,如果我们对这个对象进行修改,那么就可能造成很严重的问题。最简单的解决方案就是给对象增加一个 reset 方法。清单 4 显示的就是这样一个例子。

清单 4. 增加了 reset 方法的 Singleton 对象

<?php
class Singleton
{
  private static $instance;
  protected function __construct() { }
  private final function __clone() {}
  public static function getInstance()
  {
    if ( !isset(self::$instance) ) {
      self::$instance = new Singleton;
    }
    return self::$instance;
  }
  public static function reset()
  {
    self::$instance = null;
  }
}

现在,我们可以在每次测试之前调用 reset 方法,保证我们在每次测试过程中都会先执行 singleton 对象的初始化代码。总之,在应用程序中增加这个方法是很有用的,因为我们现在可以轻松地修改单一实例。

使用类构造函数

进行单元测试的一个良好做法是只测试需要测试的代码,避免创建不必要的对象和变量。您创建的每一个对象和变量都需要在测试之后删除。这对于文件和数据库表等 麻烦的项目来说成为一个问题,因为在这些情况下,如果您需要修改状态,那么您必须更小心地在测试完成之后进行一些清理操作。坚持这一规则的最大障碍在于对 象本身的构造函数,它执行的所有操作都是与测试无关的。清单 5 就是这样一个例子。

清单 5. 具有一个大 singleton 方法的类

<?php
class MyClass
{
  protected $results;
  public function __construct()
  {
    $dbconn = new DatabaseConnection(&#39;localhost&#39;,&#39;user&#39;,&#39;password&#39;);
    $this->results = $dbconn->query(&#39;select name from mytable&#39;);
  }
  public function getFirstResult()
  {
    return $this->results[0];
  }
}

在这里,为了测试对象的 fdfdfd 方法,我们最终需要建立一个数据库连接,给表添加一些记录,然后在测试之后清除所有这些资源。如果测试 fdfdfd完全不需要这些东西,那么这个过程可能太过于复杂。因此,我们要修改 清单 6所示的构造函数。

清单 6. 为忽略所有不必要的初始化逻辑而修改的类

<?php
class MyClass
{
  protected $results;
  public function __construct($init = true)
  {
    if ( $init ) $this->init();
  }
  public function init()
  {
    $dbconn = new DatabaseConnection(&#39;localhost&#39;,&#39;user&#39;,&#39;password&#39;);
    $this->results = $dbconn->query(&#39;select name from mytable&#39;);
  }
  public function getFirstResult()
  {
    return $this->results[0];
  }
}

我们重构了构造函数中大量的代码,将它们移到一个 init() 方法中,这个方法默认情况下仍然会被构造函数调用,以避免破坏现有代码的逻辑。然而,现在我们在测试过程中只能够传递一个布尔值 false 给构造函数,以避免调用 init()方法和所有不必要的初始化逻辑。类的这种重构也会改进代码,因为我们将初始化逻辑从对象的构造函数分离出来了。

经硬编码的类依赖性

正如我们在前一节介绍的,造成测试困难的大量类设计问题都集中在初始化各种不需要测试的对象上。在前面,我们知道繁重的初始化逻 辑可能会给测试的编写造成很大的负担(特别是当测试完全不需要这些对象时),但是如果我们在测试的类方法中直接创建这些对象,又可能造成另一个问题。清单 7显示的就是可能造成这个问题的示例代码。

清单 7. 在一个方法中直接初始化另一个对象的类

<?php
class MyUserClass
{
  public function getUserList()
  {
    $dbconn = new DatabaseConnection(&#39;localhost&#39;,&#39;user&#39;,&#39;password&#39;);
    $results = $dbconn->query(&#39;select name from user&#39;);
    sort($results);
    return $results;
  }
}

假设我们正在测试上面的 getUserList方法,但是我们的测试关注点是保证返回的 用户清单是按字母顺序正确排序的。在这种情况下,我们的问题不在于是否能够从数据库获取这些记录,因为我们想要测试的是我们是否能够对返回的记录进行排 序。问题是,由于我们是在这个方法中直接实例化一个数据库连接对象,所以我们需要执行所有这些繁琐的操作才能够完成方法的测试。因此,我们要对方法进行修 改,使这个对象可以在中间插入,如 清单 8所示。

清单 8. 这个类有一个方法会直接实例化另一个对象,但是也提供了一种重写的方法

<?php
class MyUserClass
{
  public function getUserList($dbconn = null)
  {
    if ( !isset($dbconn) || !( $dbconn instanceOf DatabaseConnection ) ) {
      $dbconn = new DatabaseConnection(&#39;localhost&#39;,&#39;user&#39;,&#39;password&#39;);
    }
    $results = $dbconn->query(&#39;select name from user&#39;);
    sort($results);
    return $results;
  }
}

Now you can directly pass in an object that is compatible with the expected database connection object and use this object directly instead of creating a new object. You can also pass in a mock object, that is, we can directly return the value we want in some calling methods in a hard-coded way. Here, we can simulate the query method of the database connection object, so that we only need to return the results without actually querying the database. Refactoring like this can also improve this approach because it allows your application to plug in different database connections when needed, rather than just binding to a designated default database connection.

Benefits of Testable Code

Obviously, writing more testable code will definitely simplify unit testing of PHP applications (as as you see in the examples shown in this article), but in the process it can also improve the design, modularity and stability of your application. We've all seen "spaghetti" code, which is stuffed with a lot of business and presentation logic in one of the main processes of a PHP application, which will undoubtedly cause serious support problems for those using the application. In the process of making the code more testable, we refactored some of the previous problematic code; these codes were not only problematic in design, but also functional. We improve the reusability of our code by making these functions and classes more versatile, and by removing hard-coded dependencies, making them more easily reusable by other parts of the application. Additionally, we are replacing poorly written code with better quality code to simplify future support of the code base.

Conclusion

In this article, we learned how to improve PHP code through some typical untestable code examples in PHP applications. Testability. We also describe how these situations can arise in applications, and then how to appropriately fix the problematic code to facilitate testing. We also learned that these code modifications can not only improve the testability of the code, but also generally improve the quality of the code and improve the reusability of the refactored code.

Related recommendations:

Detailed explanation of examples of PHP code reuse mechanism


##

The above is the detailed content of Discussion on PHP code refactoring methods. For more information, please follow other related articles on the PHP Chinese website!

Statement:
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