Heim  >  Artikel  >  Backend-Entwicklung  >  Diskussion über PHP-Code-Refactoring-Methoden

Diskussion über PHP-Code-Refactoring-Methoden

不言
不言Original
2018-04-17 13:41:421258Durchsuche

In diesem Artikel wird hauptsächlich die PHP-Code-Refactoring-Methode vorgestellt und die Konzepte, Prinzipien, zugehörigen Implementierungstechniken und Vorsichtsmaßnahmen des PHP-Code-Refactorings anhand von Beispielen ausführlich analysiert

In diesem Artikel wird die PHP-Code-Refactoring-Methode anhand von Beispielen analysiert. Teilen Sie es wie folgt mit allen als Referenz:

Da sich PHP von einer einfachen Skriptsprache zu einer ausgereiften Programmiersprache wandelt, nimmt auch die Komplexität der Codebasis einer typischen PHP-Anwendung zu. Um den Support und die Wartung dieser Anwendungen zu steuern, können wir verschiedene Testtools verwenden, um den Prozess zu automatisieren. Eines davon ist das Unit-Testen, mit dem Sie die Korrektheit des von Ihnen geschriebenen Codes direkt testen können. Allerdings sind ältere Codebasen für diese Art von Tests oft nicht geeignet. In diesem Artikel werden Refactoring-Strategien für PHP-Code beschrieben, die häufige Probleme enthalten, um den Testprozess mit gängigen Unit-Test-Tools zu vereinfachen und gleichzeitig die Abhängigkeit von der Verbesserung der Codebasis zu verringern.

Einführung

Wenn wir auf die Entwicklungsgeschichte von PHP zurückblicken, stellen wir fest, dass es sich aus einem einfachen dynamischen Skript entwickelt hat, das das beliebte ersetzte CGI-Skripte werden zu dieser Zeit zu einer vollwertigen modernen Programmiersprache. Da die Codebasis wächst, ist manuelles Testen zu einer unmöglichen Aufgabe geworden, und alle Codeänderungen, egal wie groß oder klein, haben Auswirkungen auf die gesamte Anwendung. Diese Auswirkungen können so gering sein, dass sie sich lediglich auf das Laden einer bestimmten Seite oder das Speichern eines Formulars auswirken. Sie können aber auch Probleme verursachen, die schwer zu erkennen sind, oder sie können Fehler hervorrufen, die nur unter bestimmten Bedingungen auftreten. Es kann sogar dazu führen, dass zuvor behobene Probleme in der Anwendung erneut auftreten. Zur Lösung dieser Probleme wurden viele Testtools entwickelt.

Eine der beliebtesten Methoden ist das sogenannte Funktions- oder Akzeptanztesten, bei dem die Anwendung anhand ihrer typischen Benutzerinteraktionen getestet wird. Dies ist eine großartige Möglichkeit, einzelne Prozesse in einer Anwendung zu testen. Der Testprozess kann jedoch sehr langsam sein und testet im Allgemeinen nicht, ob die zugrunde liegenden Klassen und Methoden wie erwartet funktionieren. Zu diesem Zeitpunkt müssen wir eine andere Testmethode verwenden, nämlich Unit-Tests. Das Ziel des Unit-Tests besteht darin, die Funktionalität des zugrunde liegenden Codes der Anwendung zu testen und sicherzustellen, dass dieser nach der Ausführung korrekte Ergebnisse liefert. Oft führen diese „wachsenden“ Webanwendungen langsam Legacy-Code ein, der mit der Zeit immer schwieriger zu testen ist, was es für das Entwicklungsteam schwierig macht, die Abdeckung der Anwendungstests sicherzustellen. Dies wird oft als „nicht testbarer Code“ bezeichnet. Sehen wir uns nun an, wie Sie nicht testbaren Code in Ihrer Anwendung identifizieren und wie Sie ihn beheben können.

Identifizieren von nicht testbarem Code

Eine Problemdomäne bezüglich der Nichttestbarkeit einer Codebasis, die typischerweise beim Schreiben von Code auftritt ist nicht offensichtlich. Beim Schreiben von PHP-Anwendungscode neigen die Leute dazu, den Code so zu schreiben, dass er dem Fluss von Webanfragen folgt, was normalerweise bedeutet, dass beim Anwendungsdesign ein prozessorientierterer Ansatz gewählt wird. Die Eile, ein Projekt abzuschließen oder eine Anwendung schnell zu reparieren, kann Entwickler dazu veranlassen, Abstriche zu machen, um die Programmierung schnell zu erledigen. Früher konnte schlecht geschriebener oder verwirrender Code die Untestbarkeitsprobleme in einer Anwendung verschlimmern, da Entwickler oft die am wenigsten riskante Lösung vornahmen, selbst wenn dies zu späteren Supportproblemen führen könnte. Diese Problembereiche können durch normale Unit-Tests nicht entdeckt werden.

Funktionen, die auf dem globalen Zustand basieren

Globale Variablen sind in PHP-Anwendungen praktisch. Sie ermöglichen Ihnen, einige Variablen oder Objekte in Ihrer Anwendung zu initialisieren und sie dann an anderer Stelle in der Anwendung zu verwenden. Diese Flexibilität hat jedoch ihren Preis, und die übermäßige Verwendung globaler Variablen ist ein häufiges Problem bei nicht testbarem Code. Wir können dies in Listing 1 sehen.

Listing 1. Funktionen, die vom globalen Zustand abhängen

<?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);
}

Diese globalen Variablen werfen zwei verschiedene Fragen auf. Das erste Problem besteht darin, dass Sie alle diese globalen Variablen in Ihren Tests berücksichtigen und sicherstellen müssen, dass sie auf gültige Werte gesetzt sind, die für die Funktion akzeptabel sind. Das zweite und schwerwiegendere Problem besteht darin, dass Sie den Status nachfolgender Tests nicht ändern und deren Ergebnisse ungültig machen können. Sie müssen sicherstellen, dass der globale Status auf den Status vor der Ausführung des Tests zurückgesetzt wird. PHPUnit verfügt über Tools, die Ihnen dabei helfen können, globale Variablen zu sichern und ihre Werte nach der Ausführung eines Tests wiederherzustellen, was bei diesem Problem hilfreich sein kann. Ein besserer Ansatz besteht jedoch darin, der Testklasse zu ermöglichen, die Werte dieser globalen Variablen direkt an Methoden zu übergeben. Listing 2 zeigt ein Beispiel für diesen Ansatz.

Listing 2. Ändern Sie diese Funktion, um das Überschreiben globaler Variablen zu unterstützen

<?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);
}

Dadurch wird nicht nur die Code Macht es besser testbar und macht es außerdem unabhängig von den globalen Variablen der Methode. Dadurch konnten wir den Code so umgestalten, dass keine globalen Variablen mehr verwendet werden.

Einzelne Instanz, die nicht zurückgesetzt werden kann

单一实例指的是旨在让应用程序中一次只存在一个实例的类。它们是应用程序中用于全局对象的一种常见模式,如数据库连接和配置设置。它们通常被认为是应用程序的禁忌, 因为许多开发人员认为创建一个总是可用的对象用处不大,因此他们并不太注意这一点。这个问题主要源于单一实例的过度使用,因为它会造成大量不可扩展的所谓 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;
  }
}

Jetzt können Sie direkt ein Objekt übergeben, das mit dem erwarteten Datenbankverbindungsobjekt kompatibel ist, und dieses Objekt direkt verwenden, anstatt ein neues Objekt zu erstellen. Sie können auch ein Scheinobjekt übergeben, das heißt, wir können den gewünschten Wert in einigen aufrufenden Methoden direkt und fest codiert zurückgeben. Hier können wir die Abfragemethode des Datenbankverbindungsobjekts simulieren, sodass wir nur die Ergebnisse zurückgeben müssen, ohne die Datenbank tatsächlich abzufragen. Ein solches Refactoring kann diesen Ansatz auch verbessern, da es Ihrer Anwendung ermöglicht, bei Bedarf verschiedene Datenbankverbindungen anzuschließen, anstatt sich nur an eine bestimmte Standarddatenbankverbindung zu binden.

Vorteile von testbarem Code

Natürlich wird das Schreiben von mehr testbarem Code das Unit-Testen von PHP-Anwendungen definitiv vereinfachen (wie Sie in den Beispielen sehen können). (in diesem Artikel gezeigt), aber es kann dabei auch das Design, die Modularität und die Stabilität Ihrer Anwendung verbessern. Wir alle haben „Spaghetti“-Code gesehen, der in einem der Hauptprozesse einer PHP-Anwendung mit viel Geschäfts- und Präsentationslogik vollgestopft ist, was zweifellos zu ernsthaften Supportproblemen für diejenigen führt, die die Anwendung verwenden. Um den Code besser testbar zu machen, haben wir einige der vorherigen problematischen Codes überarbeitet; diese Codes waren nicht nur problematisch im Design, sondern auch funktional. Wir verbessern die Wiederverwendbarkeit unseres Codes, indem wir diese Funktionen und Klassen vielseitiger machen und hartcodierte Abhängigkeiten entfernen, sodass sie von anderen Teilen der Anwendung leichter wiederverwendet werden können. Darüber hinaus ersetzen wir schlecht geschriebenen Code durch Code mit besserer Qualität, um die zukünftige Unterstützung der Codebasis zu vereinfachen.

Fazit

In diesem Artikel haben wir anhand einiger typischer nicht testbarer Codebeispiele in PHP-Anwendungen die Testbarkeit verbessert. Wir beschreiben auch, wie solche Situationen in Anwendungen auftreten können und wie der problematische Code dann entsprechend behoben werden kann, um das Testen zu erleichtern. Wir haben auch gelernt, dass diese Codeänderungen nicht nur die Testbarkeit des Codes verbessern können, sondern auch allgemein die Qualität des Codes verbessern und die Wiederverwendbarkeit des umgestalteten Codes verbessern können.

Verwandte Empfehlungen:

Detaillierte Erläuterung der Beispiele für PHP-Code-Wiederverwendungsmechanismen


Das obige ist der detaillierte Inhalt vonDiskussion über PHP-Code-Refactoring-Methoden. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn