PHP コードのリファクタリングについて話しましょう_PHP チュートリアル

WBOY
リリース: 2016-07-13 10:37:24
オリジナル
1016 人が閲覧しました

PHP が単純なスクリプト言語から本格的なプログラミング言語に進化するにつれて、典型的な PHP アプリケーションのコード ベースは複雑になってきました。これらのアプリケーションのサポートとメンテナンスを制御するために、さまざまなテスト ツールを使用してプロセスを自動化できます。その 1 つは単体テストで、作成したコードの正確さを直接テストできます。ただし、多くの場合、従来のコード ベースはこの種のテストには適していません。この記事では、コード ベースの改善への依存を減らしながら、一般的な単体テスト ツールを使用したテスト プロセスを簡素化するための、一般的な問題を含む PHP コードのリファクタリング戦略について説明します。

はじめに
PHP の 15 年間の開発を振り返ると、PHP は、当時人気のあった CGI スクリプトを置き換えるために使用されていた単純な動的スクリプト言語から、成熟した現代のプログラミング言語に変化したことがわかります。 コードベースが増大するにつれて、手動テストは不可能な作業となり、コードに対するすべての変更は、大小を問わず、アプリケーション全体に影響を及ぼします。これらの影響は、特定のページの読み込みやフォームの保存に影響を与えるだけの小さなものである場合もあれば、検出が難しい問題が発生したり、特定の条件下でのみ発生するエラーが発生したりする場合もあります。さらに、以前に修正された問題がアプリケーションで再発する可能性もあります。これらの問題を解決するために、多くのテスト ツールが開発されています。
一般的な方法の 1 つは、いわゆる機能テストまたは受け入れテストであり、典型的なユーザー操作を通じてアプリケーションをテストします。これはアプリケーション内の個々のプロセスをテストする優れた方法ですが、テスト プロセスが非常に遅くなる可能性があり、一般に、基になるクラスやメソッドが期待どおりに動作するかどうかのテストに失敗します。現時点では、単体テストという別のテスト方法を使用する必要があります。単体テストの目的は、アプリケーションの基礎となるコードの機能をテストし、実行後に正しい結果が得られることを確認することです。多くの場合、これらの「成長する」Web アプリケーションでは、時間の経過とともにテストがますます困難になるレガシー コードが徐々に導入され、開発チームがアプリケーションのテスト範囲を確保することが困難になります。これは、「テスト不可能なコード」と呼ばれることがよくあります。次に、アプリケーション内のテスト不可能なコードを特定する方法と、それを修正する方法を見てみましょう。
テスト不可能なコードを特定する
コードベースのテスト不可能性に関する問題領域は、コードを作成するときには明らかではないことがよくあります。 PHP アプリケーション コードを記述するとき、Web リクエストのフローに従うコードを記述する傾向があります。これは通常、アプリケーション設計によりプロセス指向のアプローチをとることを意味します。プロジェクトを完了させたり、アプリケーションをすぐに修正したりすることを急ぐあまり、開発者はコーディングを迅速に完了するために「手抜き」をする可能性があります。以前は、たとえ後続のサポート問題が発生する可能性があるとしても、開発者は最もリスクの低い修正を行うことが多かったので、コードの書き方が不十分であったり混乱を招くと、アプリケーションのテスト不可能性の問題が悪化する可能性がありました。これらの問題領域は、通常の単体テストでは発見できません。
グローバル状態に依存する関数
グローバル変数は PHP アプリケーションで便利です。これにより、アプリケーション内の一部の変数またはオブジェクトを初期化し、アプリケーション内の他の場所で使用できるようになります。ただし、この柔軟性には代償が伴い、テスト不可能なコードではグローバル変数の過剰使用が一般的な問題となります。リスト 1 でこれが起こっていることがわかります。
リスト 1. グローバル状態に依存する関数
関数 formatNumber($number)
{
グローバル $10 進数精度、$10 進数区切り記号、$thousands_separator
if ( !isset($decmal_precision) ) $decmal_precision = 2;
if ( !isset($decmal_separator) ) $decmal_separator = '.';
if ( !isset($thousands_separator) ) $thousands_separator = ',';
returnnumber_format($number, $decmal_precision, $decmal_separator,
$thousands_separator);
}
これらのグローバル変数は 2 つの異なる問題を引き起こします。最初の問題は、テストでこれらすべてのグローバル変数を考慮し、それらが関数で受け入れられる有効な値に設定されていることを確認する必要があることです。 2 番目のより深刻な問題は、後続のテストの状態を変更したり、その結果を無効にしたりできないことです。グローバル状態がテスト実行前の状態にリセットされていることを確認する必要があります。 PHPUnit には、グローバル変数をバックアップし、テストの実行後にその値を復元できるツールがあり、この問題の解決に役立ちます。ただし、より良いアプローチは、テスト クラスがこれらのグローバル変数の値をメソッドに直接渡せるようにすることです。リスト 2 は、このアプローチの例を示しています。
リスト 2. グローバル変数のオーバーライドをサポートするようにこの関数を変更します
関数 formatNumber($number, $decmal_precision = null, $decmal_separator = null,
$thousands_separator = null)
{
if ( is_null($decmal_precision) ) グローバル $decmal_precision
if ( is_null($decmal_separator) ) グローバル $decmal_separator;
if ( is_null($thousands_separator) ) グローバル $thousands_separator;
if ( !isset($decmal_precision) ) $decmal_precision = 2;
if ( !isset($decmal_separator) ) $decmal_separator = '.';
if ( !isset($thousands_separator) ) $thousands_separator = ',';
returnnumber_format($number, $decmal_precision, $decmal_separator,
$thousands_separator);
}
これにより、コードがよりテストしやすくなるだけでなく、メソッドのグローバル変数からも独立します。これにより、グローバル変数を使用しないようにコードをリファクタリングできます。
リセットできない単一インスタンス
単一インスタンスとは、アプリケーション内に一度に 1 つのインスタンスのみが存在できるように設計されたクラスを指します。これらは、データベース接続や構成設定などのグローバル オブジェクトのアプリケーションで使用される一般的なパターンです。多くの開発者は常に利用可能なオブジェクトを作成することにほとんど意味がないと考えており、あまり注意を払っていないため、これらはアプリケーションでは禁止されていると考えられています。この問題は主にシングルトンの過剰使用に起因し、その結果、スケーラブルではない、いわゆるゴッド オブジェクトが多数発生します。しかし、テストの観点から見た最大の問題は、それらが通常は変更できないことです。リスト 3 はその一例です。
リスト 3. テストするシングルトン オブジェクト
クラスシングルトン
{
プライベート静的$インスタンス
;
保護された関数 __construct() { }
プライベート最終関数 __clone() {}
パブリック静的関数getInstance()
{
if ( !isset(self::$instance) ) {
self::$instance = 新しいシングルトン;
}
self::$instance を返す
}
}
単一のインスタンスが初めてインスタンス化された後、getInstance() メソッドを呼び出すたびに実際には同じオブジェクトが返されることがわかります。このオブジェクトを変更すると、非常に大きな問題が発生する可能性があります。深刻な問題。最も簡単な解決策は、オブジェクトにリセット メソッドを追加することです。リスト 4 にその例を示します。
リスト 4. リセット メソッドが追加されたシングルトン オブジェクト
クラスシングルトン
{
プライベート静的$インスタンス
;
保護された関数 __construct() { }
プライベート最終関数 __clone() {}
パブリック静的関数getInstance()
{
if ( !isset(self::$instance) ) {
$ Self :: $ インスタンス = 新しいシングルトン
;
}
self::$instance を返す
}
パブリック静的関数reset()
{
self::$instance = null;
}
}
これで、各テストの前にリセット メソッドを呼び出して、各テスト中に最初にシングルトン オブジェクトの初期化コードが実行されるようにすることができます。全体として、このメソッドをアプリケーションに追加すると、単一のインスタンスを簡単に変更できるようになるため便利です。
クラスコンストラクターを使用する
単体テストの良い習慣は、テストする必要があるコードのみをテストし、不要なオブジェクトや変数の作成を避けることです。作成したすべてのオブジェクトと変数は、テスト後に削除する必要があります。これは、ファイルやデータベース テーブルなどの面倒なプロジェクトの場合に問題になります。このような場合、状態を変更する必要がある場合、テストの完了後にクリーンアップを行う際に注意する必要があるからです。このルールを遵守する上での最大の障害は、テストに無関係なすべての操作を実行するオブジェクト自体のコンストラクターです。リスト 5 はその例です。
リスト 5. 1 つの大きなシングルトン メソッドを含むクラス
クラスMyClass
{
$result を保護しました
パブリック関数 __construct()
{
$dbconn = 新しい DatabaseConnection('localhost','user','password');
$this->results = $dbconn->query('select name from mytable');
}
パブリック関数 getFirstResult()
{
รื่อ่าน
}
}
ここで、オブジェクトの fdfdfd メソッドをテストするには、最終的にデータベース接続を確立し、テーブルにいくつかのレコードを追加し、テスト後にこれらのリソースをすべてクリアする必要があります。 fdfdfd のテストにこれらのことがまったく必要ない場合、このプロセスは複雑すぎる可能性があります。したがって、リスト 6 に示すコンストラクターを変更します。
リスト 6. 不要な初期化ロジックをすべて無視するように変更されたクラス
クラスMyClass
{
$result を保護しました
パブリック関数 __construct($init = true)
{
if ( $init ) $this->init();
}
パブリック関数 init()
{
$dbconn = 新しい DatabaseConnection('localhost','user','password');
$this->results = $dbconn->query('select name from mytable');
}
パブリック関数 getFirstResult()
{
$this->results[ 0];
}
}
コンストラクター内の大量のコードをリファクタリングし、それらを init() メソッドに移動しました。既存のコードのロジックの破壊を避けるために、このメソッドはデフォルトで引き続きコンストラクターによって呼び出されます。ただし、init() メソッドと不要な初期化ロジックの呼び出しを避けるために、テスト中にコンストラクターにブール値 false を渡すことのみができるようになりました。このクラスのリファクタリングでは、初期化ロジックをオブジェクトのコンストラクターから分離するため、コードも改善されます。
ハードコードされたクラスの依存関係
前のセクションで紹介したように、テストを困難にするクラス設計の問題の多くは、テストする必要のないさまざまなオブジェクトの初期化に焦点を当てています。初期化ロジックが重いとテスト作成に大きな負担がかかる可能性があることは以前からわかっていましたが (特にテストでこれらのオブジェクトがまったく必要ない場合)、これらのオブジェクトをテストのクラス メソッドで直接作成すると、別の問題が発生する可能性があります。 。リスト 7 は、この問題を引き起こす可能性のあるサンプル コードを示しています。
リスト 7. メソッド内で別のオブジェクトのクラスを直接初期化する
クラスMyUserClass
{
パブリック関数 getUserList()
{
$dbconn = 新しい DatabaseConnection('localhost','user','password');
$results = $dbconn->query('ユーザーから名前を選択');
並べ替え($結果)
$results を返します。
}
}
上記の getUserList メソッドをテストしているとします。ただし、テストの焦点は、返されたユーザー リストがアルファベット順に正しくソートされていることを確認することです。この場合、テストしたいのは返されたレコードを並べ替えられるかどうかであるため、問題はデータベースからこれらのレコードを取得できるかどうかではありません。問題は、このメソッドでデータベース接続オブジェクトを直接インスタンス化するため、メソッドのテストを完了するには、これらの面倒な操作をすべて実行する必要があることです。したがって、リスト 8 に示すように、このオブジェクトを中央に挿入できるようにメソッドを変更する必要があります。
リスト 8. このクラスには、別のオブジェクトを直接インスタンス化するメソッドがありますが、オーバーライドされたメソッドも提供されます
クラスMyUserClass
{
パブリック関数 getUserList($dbconn = null)
{
if ( !isset($dbconn) || !( $dbconninstanceOf DatabaseConnection ) ) {
$dbconn = new DatabaseConnection('localhost','user','password');
}
$results = $dbconn->query('ユーザーから名前を選択');
並べ替え($結果)
$results を返します。
}
}
これで、予期されるデータベース接続オブジェクトと互換性のあるオブジェクトを直接渡し、新しいオブジェクトを作成する代わりにこのオブジェクトを直接使用できるようになりました。モック オブジェクトを渡すこともできます。つまり、ハードコードされた方法で一部の呼び出しメソッドで必要な値を直接返すことができます。ここでは、データベース接続オブジェクトのクエリ メソッドをシミュレートできるため、実際にデータベースにクエリを実行せずに結果を返すだけで済みます。このようなリファクタリングによって、アプリケーションが指定されたデフォルトのデータベース接続にバインドするだけでなく、必要に応じて別のデータベース接続を接続できるようになるため、このアプローチも改善できます。
テスト可能なコードの利点
明らかに、よりテストしやすいコードを作成すると、PHP アプリケーションの単体テストが簡素化されます (この記事で示した例からわかるように) が、その過程でアプリケーションのモジュール性と安定性も向上します。私たちは皆、PHP アプリケーションの主要プロセスの 1 つに多くのビジネス ロジックやプレゼンテーション ロジックが詰め込まれた「スパゲッティ」コードを見たことがあるでしょう。これは間違いなく、アプリケーションを使用するユーザーに重大なサポート問題を引き起こすことになります。コードをよりテストしやすくする過程で、以前に問題があったコードの一部をリファクタリングしました。これらのコードは、設計面だけでなく機能面でも問題がありました。これらの関数とクラスの汎用性を高め、ハードコーディングされた依存関係を削除して、アプリケーションの他の部分でより簡単に再利用できるようにすることで、コードの再利用性を向上させます。さらに、今後のコード ベースのサポートを簡素化するために、不適切に記述されたコードをより高品質のコードに置き換えます。
結論
この記事では、PHP アプリケーションにおけるテスト不可能なコードの典型的な例を通じて、PHP コードのテスト容易性を向上させる方法を学びました。また、アプリケーションでこのような状況がどのように発生するのか、そしてテストを容易にするために問題のあるコードを適切に修正する方法についても説明します。また、これらのコード変更により、コードのテスト容易性が向上するだけでなく、一般にコードの品質が向上し、リファクタリングされたコードの再利用性も向上することもわかりました。
http://www.bkjia.com/PHPjc/735874.html

www.bkjia.com

http://www.bkjia.com/PHPjc/735874.html技術記事 PHP が単純なスクリプト言語から本格的なプログラミング言語に進化するにつれて、典型的な PHP アプリケーションのコード ベースは複雑になってきました。これらのアプリケーションの使用を制御するには...
関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!