搜尋
首頁後端開發php教程PHP程式碼重構方法漫談

PHP程式碼重構方法漫談

Apr 17, 2018 pm 01:41 PM
php方法

這篇文章主要介紹了PHP程式碼重構方法,結合實例形式較為詳細的分析了php程式碼重構的概念、原則、相關實作技巧與注意事項,有需要的朋友可以參考下

本文實例分析了PHP程式碼重構方法。分享給大家供大家參考,具體如下:

隨著PHP 從一個簡單的腳本語言轉變為成熟的程式語言,一個典型的PHP 應用程式的程式碼庫的複雜性也隨之增大。為了控制對這些應用程式的支援和維護,我們可以使用各種測試工具來自動化流程。其中一種是單元測試,它允許您直接測試所編寫程式碼的正確性。然而,通常遺留程式碼庫是不適合進行這種測試的。本文將介紹包含常見問題的 PHP 程式碼的重構策略,以便簡化使用流行的單元測試工具進行測試的過程,同時減少改進程式碼庫的依賴性。

簡介

回顧PHP 的發展歷程,我們發現它已經從一個簡單的用來取代當時流行的CGI 腳本的動態腳本語言變成一種成熟的現代程式語言。隨著程式碼庫的成長,手動測試已經變成不可能的任務,無論是大是小,所有程式碼的變化都會對整個應用程式產生影響。這些影響可能小到只是影響某一頁面的加 載或表單保存,也可能是產生難以偵測的問題,或產生只在特定條件下才會出現的錯誤。甚至,它可能會使先前修復的問題重新出現在應用程式中。為此開發了許 多測試工具來解決這些問題。

其中一種流行的方法是所謂的功能或驗收測試,它會透過應用程式的典型使用者互動來測試這個應用程式。這是一種 很適合測試應用程式中各個進程的方法,但是測試過程可能非常慢,而且一般無法測試底層的類別和方法是否按要求正常工作。這時,我們需要使用另一種測試方法, 那就是單元測試。單元測試的目標是測試應用程式底層程式碼的功能,確保它們執行後產生正確的結果。通常,這些 「不斷增大」 的 Web 應用程式會慢慢出現越來越多久而久之難以測試的遺留程式碼,這使開發團隊很難保證應用程式測試的覆蓋率。這通常被稱為 “不可測試程式碼”。現在讓我們看看如何識別應用程式中的不可測試程式碼,以及修復這些程式碼的方法。

識別不可測試的程式碼

#關於程式碼庫不可測試性的問題域通常在編寫程式碼時是不明顯的。當編寫 PHP 應用程式程式碼時,人們傾向於按照 Web 請求的流程來編寫程式碼,這通常是在應用程式設計時採用更流程化的方法。急於完成專案或快速修復應用程式都可能促使開發人員 “走捷徑”,以便快速完成編碼。以前,編寫不當或混亂的程式碼可能會加重應用程式中的不可測試性問題,因為開發人員通常會進行風險最小的修復,即使它可能產生後續的支援問題。這些問題域都是無法透過一般的單元測試發現的。

依賴全域狀態的函數

#全域變數在 PHP 應用程式中很方便。它們允許您在應用程式中初始化一些變數或對象,然後在應用程式的其他位置使用。然而,這種靈活性是有代價的,過度使用全域變數是不可測試程式碼的一個通病。我們可以在 清單 1中看到這種情況。

清單1. 依賴全域狀態的函數

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

這些全域變數帶來了兩個不同的問題。第一個問題是您需要在測試中考慮所有這些全域變量,保證給它們設定了函數可接受的有效值。第二個問題更為嚴重, 那就是您無法修改後續測試的狀態並使它們的結果無效,您需要保證將全域狀態重設為測試運行之前的狀態。 PHPUnit 有一些工具可以幫助您備份全域變數並在測試運行後還原它們的值,這些工具能夠幫助解決這個問題。然而,更好的方法是使測試類別能夠直接給方法傳入這些全域變數的值。清單 2顯示了採用這種方法的一個範例。

清單2. 修改這個函數以支援重寫全域變數

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

這樣做不僅讓程式碼變得更具可測試性,而且也使它不依賴方法的全域變數。這使得我們能夠對程式碼進行重構,不再使用全域變數。

無法重設的單一實例

#

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

現在您可以直接傳入一個對象,它與預期資料庫連接對象相容,然後直接使用這個對象,而不是建立一個新對象。您也可以傳 入一個模擬對象,也就是我們在一些呼叫方法中,用硬編碼的方式直接傳回我們想要的值。在這裡,我們可以模擬資料庫連接物件的查詢方法,這樣我們就只需要返 回結果,而不需要真正地去查詢資料庫。進行這樣的重構也能夠改進這個方法,因為它允許您的應用程式在需要時插入不同的資料庫連接,而不是只綁定一個指定的 預設資料庫連接。

可測試程式碼的好處

顯然,編寫更具可測試性的程式碼肯定能夠簡化PHP 應用程式的單元測試(正如您在本文展示的例子中所看到的),但是在這個過程中,它也能夠改進應用程式的設計、模組化和穩定性。我們都曾經看過 “spaghetti” 程式碼,它們在 PHP 應用程式的一個主要流程中充斥了大量的業務和表現邏輯,這毫無疑問會給那些使用這個應用程式的人造成嚴重的支援問題。在讓程式碼變得更具可測試性的過程中, 我們對前面一些有問題的程式碼進行了重構;這些程式碼不僅設計上有問題,功能上也有問題。透過使這些函數和類別的用途更廣泛,以及透過刪除硬編碼的依賴性,我們 使之更容易被應用程式其他部分重用,我們提高了程式碼的可重用性。此外,我們還將編寫不當的程式碼替換成更優質的程式碼,從而簡化未來對程式碼庫的支援。

結束語

在本文中,透過PHP 應用程式中一些典型的不可測試程式碼範例,我們了解如何改進PHP 程式碼的可測試性。我們也介紹了這些情況是如何出現在應用程式中的,然後介紹如何適當地修復這些問題程式碼來方便進行測試。我們也了解了這些程式碼的修改不僅 能夠提高程式碼的可測試性,還能夠普遍改進程式碼的質量,以及提高重構程式碼的可重用性。

相關推薦:

PHP程式碼重複使用機制實例詳解


以上是PHP程式碼重構方法漫談的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
PHP的當前狀態:查看網絡開發趨勢PHP的當前狀態:查看網絡開發趨勢Apr 13, 2025 am 12:20 AM

PHP在現代Web開發中仍然重要,尤其在內容管理和電子商務平台。 1)PHP擁有豐富的生態系統和強大框架支持,如Laravel和Symfony。 2)性能優化可通過OPcache和Nginx實現。 3)PHP8.0引入JIT編譯器,提升性能。 4)雲原生應用通過Docker和Kubernetes部署,提高靈活性和可擴展性。

PHP與其他語言:比較PHP與其他語言:比較Apr 13, 2025 am 12:19 AM

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP與Python:核心功能PHP與Python:核心功能Apr 13, 2025 am 12:16 AM

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

PHP:網絡開發的關鍵語言PHP:網絡開發的關鍵語言Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP:許多網站的基礎PHP:許多網站的基礎Apr 13, 2025 am 12:07 AM

PHP成為許多網站首選技術棧的原因包括其易用性、強大社區支持和廣泛應用。 1)易於學習和使用,適合初學者。 2)擁有龐大的開發者社區,資源豐富。 3)廣泛應用於WordPress、Drupal等平台。 4)與Web服務器緊密集成,簡化開發部署。

超越炒作:評估當今PHP的角色超越炒作:評估當今PHP的角色Apr 12, 2025 am 12:17 AM

PHP在現代編程中仍然是一個強大且廣泛使用的工具,尤其在web開發領域。 1)PHP易用且與數據庫集成無縫,是許多開發者的首選。 2)它支持動態內容生成和麵向對象編程,適合快速創建和維護網站。 3)PHP的性能可以通過緩存和優化數據庫查詢來提升,其廣泛的社區和豐富生態系統使其在當今技術棧中仍具重要地位。

PHP中的弱參考是什麼?什麼時候有用?PHP中的弱參考是什麼?什麼時候有用?Apr 12, 2025 am 12:13 AM

在PHP中,弱引用是通過WeakReference類實現的,不會阻止垃圾回收器回收對象。弱引用適用於緩存系統和事件監聽器等場景,需注意其不能保證對象存活,且垃圾回收可能延遲。

解釋PHP中的__ Invoke Magic方法。解釋PHP中的__ Invoke Magic方法。Apr 12, 2025 am 12:07 AM

\_\_invoke方法允許對象像函數一樣被調用。 1.定義\_\_invoke方法使對象可被調用。 2.使用$obj(...)語法時,PHP會執行\_\_invoke方法。 3.適用於日誌記錄和計算器等場景,提高代碼靈活性和可讀性。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境