1.5K Views
October 20, 14
スライド概要
9/27に行われたレガシーコード改善勉強会で発表された資料です。
http://passmarket.yahoo.co.jp/event/show/detail/01pitgwzj67m.html
2023年10月からSpeaker Deckに移行しました。最新情報はこちらをご覧ください。 https://speakerdeck.com/lycorptech_jp
PHP版レガシーコード改善に 役立つ新パターン ヤフー株式会社 佐藤祐司 2014/9/27 レガシーコード改善勉強会 #wewlc̲jp
自己紹介 佐藤 祐司 • 2010年新卒入社 • Webエンジニア • PHP @kuidaoring
今日話すこと • 私の業務とレガシーコード • パターンの紹介 • これで幸せになれるのか
今日話すこと • 私の業務とレガシーコード • パターンの紹介 → 本日のメイン • これで幸せになれるのか
今日話すこと • レガシーコード改善の具体的な リファクタリングの具体的な方法 • 今あるコードを何とかテストで保護して、 自分が手を加える部分のテストを 書けるようにしたい
今日話すこと • レガシーコード改善の具体的な リファクタリングの具体的な方法 • 今あるコードを何とかテストで保護して、 自分が手を加える部分のテストを 書けるようにしたい
私の業務とレガシーコード
サービス サービス規模 • 58.3億PV / 月 • 1.4億UB / 月 出典:Yahoo! JAPAN 媒体資料 2014年6月改訂版 (PDF) http://i.yimg.jp/images/marketing/portal/paper/media̲sheet̲open.pdf
サービス トップページを作る部署の中の一つ PJメンバー約40名 • 開発約20名 PC ここ スマホ アプリ …
業務とレガシーコード こんなことありませんか? • 少ない自動テスト • リリース当時の開発メンバーはゼロ • どんどん開発メンバーが増える どうしてこんなふうになったのか
新規リリース時 まずはローンチを目指す • ローンチ、リリース優先 • ヒットするかはわからない ローンチだ!
リリース後 サービスの成長を目指す • 機能を増やす • メンバーも少しづつ増える 機能追加だ!
ある程度成熟して振り返ると • メンバーの入れ替わり • 歴史的経緯によるコード • 場当たり的な修正 とりあえず 直しました…! 今⽇日からよろしく お願いします!
業務とレガシーコード • その時の判断が間違いだったかは わからない • 優先順位の話 • ただしずっとそのままでいい というわけではない
業務とレガシーコード 過去には一部似た状況になったが、 • 有志による改善活動 • 社内でもノウハウがたまってきた
ユニットテスト拡充 CI整備
開発フロー整備
業務とレガシーコード どうしたか • レガシーコードを地道に改善 • レガシーコードを作りにくくする 仕組みや体制を整備 地道に少しづつ改善を進めた
パターンの紹介
パターンの紹介 • レガシーコード改善の具体的な リファクタリングの具体的な方法 • 今あるコードを何とかテストで保護して、 自分が手を加える部分のテストを 書けるようにしたい
PHPって • Webに特化 • 置けば動く • HTMLに埋め込める • テンプレートエンジン • 標準関数が豊富
PHPにおけるレガシーコード • 環境に依存 • 構造を持たない • スーパーグローバル変数 • $̲GET, $̲POST, $̲SESSION など • 不用意なexit
PHPにおけるレガシーコード サンプルコードを用いて説明 • adminユーザと一般ユーザで見せる メニューを変更する
どう対応するのか 1. テストを書けるようにする 2. テストで保護する 3. リファクタリング 4. テスト、リファクタリングを繰り返す
どう対応するのか 1. テストを書けるようにする 2. テストで保護する 3. リファクタリング 4. テスト、リファクタリングを繰り返す
なにが問題になるか • DBに接続する • exitでスクリプトが終了 • getパラメータが必要 • ビューとロジックの混在
DBに接続
exitで終了
スーパーグローバル 変数を参照して パラメータ取得
ビューとロジックが 混在
どうにかしてテストを 書けるようにするためのパターン
• 関数オーバーライド • ラップ関数
関数オーバーライド
関数オーバーライド • 名前空間を使って組み込み関数などを 上書き • 実際には上書きしてない • 元ネタ PHPでネイティブ関数を含むコードのテスタビリティを上げる2つの方法 - 絶品ゆどうふのタレ http://yudoufu.hatenablog.jp/entry/20110808/1312828535 • 名前空間が接合部になる
関数オーバーライド プロダクトコード index.php func() 外部 リソース
関数オーバーライド プロダクトコード index.php func() func() テストコード 外部 リソース
接合部 接合部とは、その場所を直接変更しなくて も、プログラムの振る舞いを変えることの 出来る場所である レガシーコード改善ガイドより
コンストラクタで 渡すオブジェクトによって 振る舞いを変更できる
名前空間 PHP5.3から導入 PHP: 名前空間 ‒ Manual http://php.net/manual/ja/language.namespaces.php 他の言語のパッケージやモジュールに相当
名前空間 名前空間の影響を受けるもの • class • interface • trait(5.4以降) • 関数 • 定数(const)
定義
参照
名前空間 • 名前空間の中であれば、 組み込み関数やグローバルな関数と 同じ名前の関数を定義できる • 同じ名前空間内であれば、 名前空間の指定を省略できる
組み込み関数と同じ 名前で定義できる
名前空間内の関数が 呼ばれる
関数オーバーライド どういう時に有効か • 上書きしたい対象が関数でしか 用意されていない 外部の影響を受ける組み込み関数を そのまま使っている場合など
ラップ関数
ラップ関数 変数の参照などを直接行うのではなく 関数を経由して行うようにする
どう対応するのか
なにが問題になるか • DBに接続する • exitでスクリプトが終了 • getパラメータが必要 • ビューとロジックの混在
どう対応するのか 1. テストを書けるようにする • 環境の分離 • スーパーグローバル変数の間接参照 • exitの検討 2. テストで保護する 3. リファクタリング 4. テスト、リファクタリングを繰り返す
どう対応するのか 1. テストを書けるようにする • 環境の分離 • スーパーグローバル変数の間接参照 • exitの検討 2. テストで保護する 3. リファクタリング 4. テスト、リファクタリングを繰り返す
環境の分離 テスト実行時にもDBに接続しにいってしま う • DBに接続する関数をなんとかしたい • 関数オーバーライドを使う
環境の分離 1. テスト対象のスクリプトに 名前空間の定義を追加 2. テストコードでテスト対象と 同じ名前で名前空間の定義を追加 3. 上書きしたい関数と同じ名前の 関数をテストコードで定義
環境の分離 1. テスト対象のスクリプトに 名前空間の定義を追加 2. テストコードでテスト対象と 同じ名前で名前空間の定義を追加 3. 上書きしたい関数と同じ名前の 関数をテストコードで定義
環境の分離 1. テスト対象のスクリプトに 名前空間の定義を追加 2. テストコードでテスト対象と 同じ名前で名前空間の定義を追加 3. 上書きしたい関数と同じ名前の 関数をテストコードで定義
すでに他の名前空間が あればその下に追加
環境の分離 1. テスト対象のスクリプトに 名前空間の定義を追加 2. テストコードでテスト対象と 同じ名前で名前空間の定義を追加 3. 上書きしたい関数と同じ名前の 関数をテストコードで定義
関数オーバーライド プロダクトコード index.php db̲get̲user() DB
関数オーバーライド プロダクトコード index.php db̲get̲user() db̲get̲user() テストコード DB
注意 名前空間を定義することで 関数が呼び出せなくなる場合がある
名前空間の定義を 追加
参照できなくなる
名前空間の影響 どうするか • 関数を名前空間の外に再定義
どう対応するのか 1. テストを書けるようにする • 環境の分離 • スーパーグローバル変数の間接参照 • exit, dieの検討 2. テストで保護する 3. リファクタリング 4. テスト、リファクタリングを繰り返す
スーパーグローバル変数 変数なので代入して書き換えが可能 • 他のテストケースに影響してしまう • 変数を直接参照しなければいい • ラップ関数を使う
どう対応するのか 1. テストを書けるようにする • 環境の分離 • スーパーグローバル変数の参照 • exitの検討 2. テストで保護する 3. リファクタリング 4. テスト、リファクタリングを繰り返す
exitの検討 exitのなにが問題になるか • スクリプトを終了 PHPUnit自体も終了する • exitは関数ではない
実行されない
exit、dieの必要性の検討 成功・失敗も わからず終了
exitの検討 exitは言語構造、予約語 • 名前空間内でも「exit」という 名前の関数を定義できない • 「関数オーバーライド」が使えない
exitの検討 どうするか • returnの代わりに使ってませんか • returnに変える • exitする部分をラップ関数にする • その後に関数オーバーライドを使う
どう対応するのか 1. テストを書けるようにする 2. テストで保護する 3. リファクタリング 4. テスト、リファクタリングを繰り返す
テスト • ビューとロジックが混ざっているので ロジックのみの検証はおそらく不可能 • 条件によって変わるビューを検証する • assertRegex, assertContains • HTMLであればassertTagなど
どう対応するのか 1. テストを書けるようにする 2. テストで保護する 3. リファクタリング 4. テスト、リファクタリングを繰り返す
リファクタリング まずはベタなものを適切な単位に分離 • ファイルを分離 • 関数に分離 • クラスに分離
課題 globalが邪魔をする • グローバル変数ではなくなる ことによってうまく動作しなくなる • 「コンパイラまかせ」が使えないので 動かすまでわからない
課題 • 関数内でグローバル変数を参照している 部分を引数に置き換えるように リファクタリングする • 地道に。。。 • 参考 テスト不能な PHP コードをリファクタリングするための戦略 http:/www.ibm.com/developerworks/jp/opensource/library/osrefactoringphp/
これで幸せになれるのか
これで幸せになれるのか これだけで根本解決はできない • とりあえずテストは書けるようになった • 適用できるケースは それほど多くない(かも) • 今回は「今」できること
レガシー 小改善 テストで 保護 リファクタ リング イケてる
これで幸せになれるのか 全体を考えて設計の変更などを 行う必要がある • フラットなPHPからフレームワークへ • 地道にテストコードを増やしていく 継続的な改善が必要
ありがとうございました