Share
Embed
[PHPカンファレンス沖縄2024]「無理なくできるだけ安全に」テストもないレガシーコードをリファクタリングするテクニック
PHPCON沖縄2024 で登壇した際の資料です。
セッション詳細: https://fortee.jp/phpcon-okinawa-2024/proposal/4584f78e-6281-450d-8e8d-41495a89a6b7
#phpcon_okinawa #track_b
Z.O.E.
September 27, 2024
Transcript
60 slides
-
X(旧twitter): @for__3 #phpcon_okinawa #track_b リファクタしたいけど、つらい • リファクタしたいところ=複雑で分かりづらい ◦ 機能追加、改修した際にどこに影響があるかわからない ◦
結果として→ バグりやすい=⼿を出しづらい 6 リファクタしたい 複雑でバグりやすい -
X(旧twitter): @for__3 #phpcon_okinawa #track_b リファクタしたいけど、つらい • リファクタしたいところ=複雑で分かりづらい ◦ 機能追加、改修した際にどこに影響があるかわからない ◦
結果として→ バグりやすい=⼿を出しづらい 7 リファクタしたい ただリファクタすると‧‧‧ 複雑でバグりやすい 障害‧不具合発⽣! -
X(旧twitter): @for__3 #phpcon_okinawa #track_b リファクタしたいけど、つらい • リファクタしたいところ=複雑で分かりづらい ◦ 機能追加、改修した際にどこに影響があるかわからない ◦
結果として→ バグりやすい=⼿を出しづらい 8 リファクタしたい 複雑でバグりやすい 障害‧不具合発⽣! ただリファクタすると‧‧‧ リファクタするための テクニックを使おう! -
X(旧twitter): @for__3 #phpcon_okinawa #track_b • リファクタをするかしないかの判断 • ⼿強いレガシーコードをリファクタするテクニック ◦ リファクタ対象をよく理解する
◦ 利⽤箇所が多い関数をリファクタする ◦ でかいコードは細かく分割していく もくじ 9 -
X(旧twitter): @for__3 #phpcon_okinawa #track_b どういう状況ならリファクタしたほうが良いのか? • 今後も継続的に改修することがわかっている • すでに不具合などが定期的に発⽣している 12
技術負債返済 しないときに かかるコスト 技術負債返済にか かるコスト -
X(旧twitter): @for__3 #phpcon_okinawa #track_b 余談:リファクタをする⽬的を明確にしておくのも⼤事 • なんのためにリファクタするのか • どういう⽅針でリファクタするのか •
どうなったらリファクタできたと⾔えるのか • ‧‧‧etc 20 -
X(旧twitter): @for__3 #phpcon_okinawa #track_b • リファクタをするかしないかの判断 • ⼿強いレガシーコードをリファクタするテクニック ◦ リファクタ対象をよく理解する
◦ 利⽤箇所が多い関数をリファクタする ◦ でかいコードは細かく分割していく もくじ 21 -
X(旧twitter): @for__3 #phpcon_okinawa #track_b 補⾜: よりきれいにコードを分割していく • コードをよりリーダブルで保守性の⾼いものにしていくた めには、凝集度を上げ結合度を下げていく必要がある 23
凝集度 1. 機能的凝集 2. 逐次的凝集 3. 通信的凝集 4. ⼿順的凝集 5. ⼀時的凝集 6. 論理的凝集 7. 偶発的凝集 結合度 1. 内容結合 2. 共通結合 3. ハイブリッド結合 4. 制御結合 5. スタンプ結合 6. データ結合 本セッションで紹介するテクニックは基本的に結合度を下げるためのテクニック 分かりやすく 機能追加し易い 影響受けにくく 壊れにくい -
X(旧twitter): @for__3 #phpcon_okinawa #track_b リファクタ対象をよく理解する • レガシーコードはソースコードから理解できない事が多い • ログを仕込みながら、わかったことはコードコメントに書 いていく
◦ 特に利⽤箇所と利⽤⽅法を明確にしておき、テストが書けるも のは書いておく 27 -
X(旧twitter): @for__3 #phpcon_okinawa #track_b リファクタ対象をよく理解する • レガシーコードはソースコードから理解できない事が多い • ログを仕込みながら、わかったことはコードコメントに書 いていく
◦ 特に利⽤箇所と利⽤⽅法を明確にしておき、テストが書けるも のは書いておく • 過去のPRやコミットメッセージなども活⽤するとよい 28 -
X(旧twitter): @for__3 #phpcon_okinawa #track_b 31 function traceLog(){ $traces = debug_backtrace(1);
foreach($traces as $i => $trace){ if (!empty($trace['object']) && is_object($trace['object'])) { $trace['object'] = get_class($trace['object']).':class'; } if (is_array($trace['args'])) { foreach ($trace['args'] AS &$arg) { if (is_object($arg)) { $arg = get_class($arg).'::class'; } } } $text[$i] = ."#".$i." ".$trace['file'].'('.$trace['line'].') '; $text[$i].= (!empty($trace['object'])?$trace['object'].$trace['type']:''); $text[$i].= $trace['function'].'('.implode(', ',$trace['args']).')'; } echo implode("\n", $text) . "\n"; } debug_backtrace() サンプルコード -
X(旧twitter): @for__3 #phpcon_okinawa #track_b 32 public function case2($collection) { +
traceLog(); $collection = $collection ->filter(function ($i) { return $i % 2 === 0; }) ->values() ->map(function ($i) { return $i * 3; }) debug_backtrace() サンプルコード -
X(旧twitter): @for__3 #phpcon_okinawa #track_b #0 /var/www/app/Console/Commands/MeasurePerformanceTrait.php(59) App\Console\Commands\ExampleLazyCollection:class->traceLog() #1 /var/www/app/Console/Commands/ExampleLazyCollection.php(36) App\Console\Commands\ExampleLazyCollection:class->case2(Illuminate\Support\LazyColl
ection::class) #2 /var/www/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36) App\Console\Commands\ExampleLazyCollection:class->handle() #3 /var/www/vendor/laravel/framework/src/Illuminate/Container/Util.php(41) Illuminate\Container\{closure}() #4 /var/www/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(93) unwrapIfClosure(Closure::class) #5 /var/www/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35) callBoundMethod(Illuminate\Foundation\Application::class, Array, Closure::class) 33 debug_backtrace() サンプルコード -
X(旧twitter): @for__3 #phpcon_okinawa #track_b • リファクタ対象をよく理解する ◦ debug_backtrace()を使う • 利⽤箇所が多い関数をリファクタする
• でかいコードは細かく分割していく ⼿強いレガシーコードをリファクタするテクニック 34 -
X(旧twitter): @for__3 #phpcon_okinawa #track_b phpstan/phpstan-deprecation-rules を利⽤する • リファクタ対象に @deprecated をつけ
phpstan-deprecation-rules を使うことで利⽤している箇所 を⼀覧化することもできる 36 -
X(旧twitter): @for__3 #phpcon_okinawa #track_b phpstan/phpstan-deprecation-rules を利⽤する • リファクタ対象に @deprecated をつけ
phpstan-deprecation-rules を使うことで利⽤している箇所 を⼀覧化することもできる • ただし、こちらの場合はあくまで静的解析によるもので、 どういう引数が使われてるかまでは出せない 37 -
X(旧twitter): @for__3 #phpcon_okinawa #track_b phpstan/phpstan-deprecation-rules を利⽤する • リファクタ対象に @deprecated をつけ
phpstan-deprecation-rules を使うことで利⽤している箇所 を⼀覧化することもできる • ただし、こちらの場合はあくまで静的解析によるもので、 どういう引数が使われてるかまでは出せない • CIと組み合わせてdeprecatedが増えてないことをチェックするのも良い 38 -
X(旧twitter): @for__3 #phpcon_okinawa #track_b • リファクタ対象をよく理解する ◦ debug_backtrace()を使う ◦ phpstan/phpstan-deprecation-rules
を利⽤する • 利⽤箇所が多い関数をリファクタする • でかいコードは細かく分割していく ⼿強いレガシーコードをリファクタするテクニック 40 -
X(旧twitter): @for__3 #phpcon_okinawa #track_b 43 $ find . -name "*.php"
| xargs grep 'mbTrim' ./src/app/Http/Controllers/Front/ArticlesController.php: $name = \Text::mbTrim($request->input('name')); ./src/app/Http/Controllers/Front/ArticlesController.php: $title = \Text::mbTrim($request->input('title')); ./src/app/Http/Controllers/Front/ArticlesController.php: $body = \Text::mbTrim($request->input('body')); …etc 例) mbTrimをリファクタしたい -
X(旧twitter): @for__3 #phpcon_okinawa #track_b 44 class Text { public static
function mbTrim($text) { // 元の処理 } + public static function mbTrim2($text) + { + // 新しい処理 + } 例) 新しい処理をmbTrim2として追加 -
X(旧twitter): @for__3 #phpcon_okinawa #track_b 利⽤箇所が多い関数をリファクタするのは⼤変 • 利⽤箇所が多い関数をリファクタする際は、既存関数と別 で関数を分けてリファクタしよう • 既存関数と新規関数をどちらも実⾏して、実⾏結果が違う
ときだけログなどに出⼒するようにし、実際のロジックに は既存関数の処理を使うと良い 45 -
X(旧twitter): @for__3 #phpcon_okinawa #track_b 46 //利用箇所 $name = \Text::mbTrim($request->input('name')); +
$name2 = \Text::mbTrim2($request->input('name')); + // $name と $name2の結果が違うときだけログを出力 + if ($name !== $name2) { + \Log::info('name: ' . $name . ' name2: ' . $name2); + } 例) 追加したmbTrim2を処理にいれる -
X(旧twitter): @for__3 #phpcon_okinawa #track_b 利⽤箇所が多い関数をリファクタするのは⼤変 • 利⽤箇所が多い関数をリファクタする際は、既存関数と別 で関数を分けてリファクタしよう • 既存関数と新規関数をどちらも実⾏して、実⾏結果が違う
ときだけログなどに出⼒するようにし、実際のロジックに は既存関数の処理を使うと良い 47 → 要は既存ロジックの実⾏結果を元にスナップショットテストを やっているようなイメージ -
X(旧twitter): @for__3 #phpcon_okinawa #track_b 49 //利用箇所 $name = \Text::mbTrim($request->input('name')); +
// featureフラグのときはリファクタしたmbTrim2を使う + if ($onRefactor) { + $name = \Text::mbTrim2($request->input('name')); + } 例) 特定条件のときだけリファクタした関数を使う -
X(旧twitter): @for__3 #phpcon_okinawa #track_b 50 //利用箇所 $name = \Text::mbTrim($request->input('name')); +
// featureフラグのときはリファクタしたmbTrim2を使う + if ($onRefactor) { + $name = \Text::mbTrim2($request->input('name')); + } or + // 1%の確率でリファクタしたmbTrim2を使う + if (mt_rand(1, 100) <= 1) { + $name = \Text::mbTrim2($request->input('name')); + } 例) 特定条件のときだけリファクタした関数を使う -
X(旧twitter): @for__3 #phpcon_okinawa #track_b • リファクタ対象をよく理解する ◦ debug_backtrace()を使う ◦ phpstan/phpstan-deprecation-rules
を利⽤する • 利⽤箇所が多い関数のリファクタテクニック • でかいコードは細かく分割していく ⼿強いレガシーコードをリファクタするテクニック 52 -
X(旧twitter): @for__3 #phpcon_okinawa #track_b でかいコードは細かく分割していく • ⾏数が多く処理が⼤きい関数は、処理の塊単位などで分け て⾏くとよい ◦ ただし、使⽤変数やスコープが変わるので注意
• データの取得‧整形‧保存を⼀つの関数でやらない • ここでも分割前後で関数を実⾏するように動作確認をする 56 -
X(旧twitter): @for__3 #phpcon_okinawa #track_b 補⾜: よりきれいにコードを分割していく • コードをよりリーダブルで保守性の⾼いものにしていくた めには、凝集度を上げ結合度を下げていく必要がある 57
本セッションで紹介するテクニックは基本的に結合度を下げるためのテクニック 分かりやすく 機能追加し易い 影響受けにくく 壊れにくい 凝集度 1. 機能的凝集 2. 逐次的凝集 3. 通信的凝集 4. ⼿順的凝集 5. ⼀時的凝集 6. 論理的凝集 7. 偶発的凝集 結合度 1. 内容結合 2. 共通結合 3. ハイブリッド結合 4. 制御結合 5. スタンプ結合 6. データ結合 -
X(旧twitter): @for__3 #phpcon_okinawa #track_b 59 株式会社ウィルゲート 10年⽬ シニアマネージャー∕VPoE やってること -
教育∕1on1∕採⽤ - PM∕SRE∕インフラ 興味あること - オブザーバビリティ∕⾃動化∕開発⽣産性∕PHP - 沖縄の美味しいご飯 池添 誠(いけぞえ まこと)