最もよくある質問は、マネージャーとリファクタリングについて話す方法です。マーティン・ファウラー、リファクタリング。 既存のコードの改善»
そのような場合、私は幾分物議を醸すアドバイスをします:彼に何も言わないでください!
コードの非推奨、サポートの難しさ、予測不可能なバグ-これらの用語は、製品が開発されるにつれて開発者の生活の中で次々と現れます。 前者が開発者の利益になる可能性が高い場合、後者は直接的なビジネス上の問題です。
この記事では、大規模なプロジェクトを書き直した経験を共有し、ボーナスとして、私たちを助けてくれて、できればこの興味深い道を始めるのに役立つコードをいくつか持ってきたいと思います。
デブリーフィング
問題
それらは通常、よく知られたシナリオに従って始まります:
- チーフは「私たちには何の役にも立ちません、メインのクライアントは危険にさらされています!」
- または、実現不可能なチップを固定する要求があるマネージャー。
- あまり頻繁ではありませんが、開発者は「レガシー」コードを詳しく調べることにうんざりしているため、すべてを書き直すことにしました。
そして通常、これは一般的なinりとフラストレーションで終わります。なぜなら、チップは緊急に必要であり、顧客も待つことができず、悲しい遺産のために、チームは散らばろうとしているからです。 状況は、「リファクタリングのためのお金」(ビジネスの面でのチームの不作為)の欠如によって損なわれています
最後の点については、すべてを書き直そうとしているチームの新しい人の状況を考慮していないことを付け加える必要がありますが、プロジェクトの開発について説明したアプローチを簡単に正当化できます。
タスク
- プロジェクトをモダンアーキテクチャに転送する
- リファクタリングコストを最小限に抑える
実装の概略図
私たちのプロジェクトはもともとKohanaで書かれていたので、Symfony2で書き直したので、すべての例はこれらのシステムのコンテキストで与えられています。 ただし、このアプローチはどのフレームワークにも適用できます。 前提条件は、アプリケーションへの単一のエントリポイントです。
最初に、アプリケーションはエントリポイント「app_kohana.php」を介してユーザーリクエストを処理します
新しいシステムの初期エントリポイントをラップして、一種の「プロキシ」を編成します。
リファクタリング
コントローラー-古いシステムのラッパー
アイデアは非常にシンプルで、次のとおりです。
- 2つのシステムを並行してデプロイします(kohana + symfony)
- エントリポイントを新しいものに変更します(symfony)
- デフォルトですべてのリクエストを古いシステムに転送するユニバーサルコントローラーを編成します
最初の2つのポイントで問題が発生しない場合、3番目のポイントが重要です。落とし穴が見つかる可能性があるためです。
最初に思い浮かぶのは、ob_startにインクルードをラップすることです。 やってみましょう:
class PassThroughController extends Symfony\Bundle\FrameworkBundle\Controller\Controller { public function kohanaAction() { ob_start(); $kohanaPath = $this->container->getParameter('kernel.root_dir') . '/../app_kohana.php'; include $kohanaPath; $response = ob_get_clean(); return new Response($response); } }
ユニバーサルコントローラーのルーティング
application.passthrough_kohana: path: /{slug} defaults: _controller: ApplicationBundle:PassThrough:kohana requirements: slug: .*
システムはすでにこの形式で動作していますが、しばらくすると最初のバグが発生します。 たとえば、ajaxエラーの誤った処理。 または、ウェブサイトのエラーは、404ではなくコード200で示されます。
ここでは、バッファがヘッダーを飲み込むため、ヘッダーを明示的に処理する必要があることを理解しています
class PassThroughController extends Symfony\Bundle\FrameworkBundle\Controller\Controller { public function kohanaAction() { ob_start(); $kohanaPath = $this->container->getParameter('kernel.root_dir') . '/../app_kohana.php'; include $kohanaPath; $headers = headers_list(); $code = http_response_code(); $response = ob_get_clean(); return new Response($response, $code, $headers); } }
その後、飛行は正常です。
新しいシステムの機能に影響を与える古いシステムの問題
終了()
システムの中で、コントローラーの最後にexit()が喜んで呼び出される場所を見つけました。 これは、例えばYii(CApplication :: end())で実践されています。 これは、新しいシステムでイベントモデルの使用を開始し、コントローラーの実行後に発生するイベントを処理するまで、特定の頭痛の種にはなりません。 最も顕著な例はSymfony Profilerです。これは、exitを含むリクエストに対して機能しなくなります。
この場合を念頭に置いて、必要に応じて適切な対策を講じる必要があります。
ob_end _ *()
ob_end関数の不適切な使用は、新しいプロキシコントローラのバッファをクリアすることにより、新しいシステムの作業を簡単に中断させる可能性があります。 それも念頭に置いておく必要があります。
Kohana_Controller_Template :: $ auto_render
この変数は、グローバルテンプレートでコントローラーから受信したデータを自動的にレンダリングする役割を果たします(使用するテンプレートエンジンに大きく依存します)。 これにより、新しいシステムの適応中に、たとえばjsonが単純なecho $ jsonで表示される場所でのデバッグの時間を節約できます。 exit(); 。 コントローラーは次のようになります。
$this->auto_render = false; echo $json; return;
他に何を大事にする
上記のエントリポイントは理想的です。 最初のエントリポイントはapp.phpであり、リファクタリング後も同じままにする必要がありました(多数のサーバーの再構成は無駄に見えました)。 次のアルゴリズムが選択されました。
- app.phpの名前をapp_kohana.phpに変更します
- Symphonyエントリポイントはapp.phpに配置されます
- 利益
そして、kohanで同じファイルを介して起動されたコンソールコマンドを除き、すべてが巻き上げられたように見えます。 したがって、新しいapp.phpの開始時に、下位互換性のために次の松葉杖が作成されました。
if (PHP_SAPI == 'cli') { include 'app_kohana.php'; return; }
リファクタリング後の生活
新しいコントローラー
すべての新しいコントローラーをsymfonyで作成しようとします。 分離はルーティングレベルで行われ、必要なものは「ユニバーサル」ルートの前に追加され、Kohanaはそれ以上ロードしません。 これまでのところ、新しいシステムではajaxコントローラーのみを記述しているため、テンプレート(Twig)の再利用の問題は未解決のままです。
DBと構成
データベースにアクセスするために、標準のDoctrineメソッドを使用して現在のデータベースからモデルが生成されました。 必要に応じて、データベースを操作するための新しいメソッドがリポジトリに追加されます。 ただし、データベース接続構成は既存のKohanaから使用されます。 これを行うために、Kohana構成からデータをプルし、それらをsymphony構成パラメーターに変換する構成ファイルが書き込まれます。 構成検索のロジックは、プラットフォームに依存しますが、新しいシステムのKohanaクラスを接続しないように複製されます。
アプリケーション/リソース/ config / kohana.php
/** @var \Symfony\Component\DependencyInjection\ContainerBuilder $container */ $kohanaDatabaseConfig = []; $kohanaConfigPath = $container->getParameter('kernel.root_dir') . '/config'; if (!defined('SYSPATH')) { define('SYSPATH', realpath($container->getParameter('kernel.root_dir') . '/../vendor/kohana/core') . '/'); } $mainConfig = $kohanaConfigPath . '/database.php'; if (file_exists($mainConfig)) { $kohanaDatabaseConfig = include $mainConfig; } if (isset($_SERVER['PLATFORM'])) { $kohanaEnvConfig = $kohanaConfigPath . '/' . $_SERVER['PLATFORM'] . '/database.php'; if (file_exists($kohanaEnvConfig)) { $kohanaDatabaseConfig = array_merge($kohanaDatabaseConfig, include $kohanaEnvConfig); } } if (empty($kohanaDatabaseConfig['default'])) { throw new \Symfony\Component\Filesystem\Exception\FileNotFoundException('Could not load database config'); } $dbParams = $kohanaDatabaseConfig['default']; $container->getParameterBag()->add([ 'database_driver' => 'pdo_mysql', 'database_host' => $dbParams['connection']['hostname'], 'database_port' => null, 'database_name' => $dbParams['connection']['database'], 'database_user' => $dbParams['connection']['username'], 'database_password' => $dbParams['connection']['password'], ]);
設定は標準的な方法で接続されます
アプリケーション/ DependencyInjection / ApplicationExtension.php
class ApplicationExtension extends Symfony\Component\HttpKernel\DependencyInjection\Extension { public function load(array $configs, ContainerBuilder $container) { $loader = new Loader\PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('kohana.php'); } }
続行方法:サービスに機能を追加する
コハナからシンフォニーへのさらなる移行は、シンフォニーサービスに機能を追加し、DIコンテナーを介して古いシステムでそれらを使用することに非常に適しています。 そのため、シンフォニーをプロジェクトに接続する前にDIコンポーネントの使用を開始したため、このプロセスは非常にスムーズに進みましたが、スクラッチを妨げるものは何もありません。 主なタスクは、シンフォニーからDIコンテナをkohanにスローすることです。 静的プロパティを介してKohanaスタイルでそれを行いました。別のフレームワークでは適切なアプローチを見つけることができます。
cohanaシステムクラスを再定義し、そこにコンテナのプロパティを追加します。
class Kohana extends Kohana_Core { /** * @var Symfony\Component\DependencyInjection\ContainerBuilder */ public static $di; }
そして、kohanの初期化とコントローラーコードの実行の間に、このプロパティにDIコンテナーを配置するために、さらに数回の不正行為を行う必要があります。 これを行うために、初期化ファイルapp_kohana.phpを2つの部分に分割し、システムの初期化とコントローラー自体の起動を直接強調表示します。
/** app_kohana_init.php */ // , bootstrap /** app_kohana_run.php */ echo Request::factory(TRUE, array(), FALSE) ->execute() ->send_headers(TRUE) ->body(); /** app_kohana.php */ include 'app_kohana_init.php'; include 'app_kohana_run.php';
コントローラーを変更し、app_kohana.phpと同様の操作を行いますが、インクルード間にコンテナー転送を追加します
public function kohanaAction() { ob_start(); $kohanaPath = $this->container->getParameter('kernel.root_dir') . '/..'; include $kohanaPath . '/app_kohana_init.php'; \Kohana::$di = $this->container; include $kohanaPath . '/app_kohana_run.php'; $headers = headers_list(); $code = http_response_code(); $response = ob_get_clean(); return new Response($response, $code, $headers); }
その後、古いシステムでは、DIコンテナと、EntityManagerや新しい教義モデルなど、新しいシステムで発表されたすべてのサービスを使用できます。
最後に
実装の利点
- 私たちは、システムのさらなる開発のための第一歩を踏み出しました。
- 新しいシステムは、古いシステムから独立しています。 すべての新しいコードは古いコードなしで機能
- 最短時間
実装の短所
- システムの古い部分で作業中の「ラッパー」用の追加オーバーヘッドリソース。 ただし、古いシステムの遅延と比較すると、オーバーヘッド(メモリとプロセッサの両方から)は無視できます。
- 新しいシステムは、古いシステムから独立しています。 古いコードを新しいコードで使用することはできませんが、書き直すことにしたため、これはかなりプラスです。
- モデルを2か所でサポートする必要があります。
最後まで読んでくれてありがとう、リファクタリングに成功して、古いコードから蓄積された塵を払いのけてくれることを願っています!
そして、図の中のひどいフォントでごめんなさい:(