ねらい 309行のレガシーコードから読みにくさの原因を自分で探し、出力を1文字も変えずに全部潰します。
この演習で扱うもの 扱うのは LegacyReportService という帳票出力のクラスです。309行あります。受注データから3種類のレポートを文字列で組み立てて返します。月次売上、顧客別サマリー、高額受注アラートの3つです。
データはクラスの中に8件持っています。受注番号、顧客名、商品名、数量、単価、ステータス、受注日。これを Map に詰めて、リストで持っています。外部とはつながっていないので、手元で何度実行しても同じ結果が出ます。整理の前後を比べるのに向いた題材です。
このクラスには、読みにくさの原因が8種類・のべ100箇所以上入っています。意図的に仕込んだものです。
なぜこれをやるのか リファクタリングは、動きを1ミリも変えずに読みやすさだけを上げる作業です。普通の修正と目的が逆で、動きが変わったら失敗になります。
なぜ AI の時代にこれが要るのか。生成される量が増えるからです。自分で書いたコードなら中身を知っていますが、AI が書いたコードは知らないまま手元に残ります。その状態で読みにくいコードが積み上がると、次に直すときに誰も手を出せなくなります。
もう1つあります。読みにくいコードは AI にとっても読みにくい。変数が cnt や tmp のままだと、こちらの意図が伝わらず、見当違いの修正が返ってきます。整理しておくと、次に AI へ頼むときの精度が上がります。
ここまで出たら完了 8種類のうち何種類を潰したか、種類の名前で言える 潰すたびに diff を取り、そのすべてで何も表示されなかった before.txt と after.txt の「合計金額(税込): 10,571,000円」が一致している 潰せなかった種類について、なぜ手を出さなかったかを説明できる 完了時。diff が何も表示しないのが正解です さわるファイル 書く exercises/day2/LegacyReportService.java 整理する対象。309行 新規 before.txt handson 直下。整理前の出力 新規 after.txt handson 直下。diff で比べる
まず自分で考える [5min] ファイルを開いて、読みにくいと感じた箇所を探してください。AI はまだ使いません。全部見つける必要はありません。何種類見つけられるかを試してください。
「コードスメール」と呼ばれる、よくある形が8種類入っています。名前を知らなくても、読みにくいと感じたらそれがスメールです。行番号と、なぜ読みにくいかをメモしてください。
同じような処理が何度も出てくる 1つのメソッドが長すぎて、何をしているか一目で分からない 数字がそのまま書かれていて、何の数字か分からない 変数の名前から中身が想像できない 括弧の入れ子が深くて、条件を追えない AI と詰める 自分が見つけたものを AI に見せて、見落としを出させます。ここで初めて AI を使います。先に自分で探しておくと、返ってきた指摘のうちどれが自分の見落としかが分かります。いきなり AI に探させると、一覧を眺めて終わりになります。
返ってきたら、自分のメモと突き合わせてください。何種類見つけられたか、何を見落としたかが今日の収穫です。
@exercises/day2/LegacyReportService.java
このコードの読みにくい箇所を自分で探しました。
(自分が見つけたものを貼る)
他にコードスメールがあれば、種類の名前と行番号を挙げてください。
まだ直さないでください。一覧だけお願いします。 手を動かす 1
実行
潰す前に、いまの出力をファイルに落とします。これが安全網です。ターミナルで java -Dfile.encoding=UTF-8 exercises/day2/LegacyReportService.java > before.txt と打ちます。中を開いて「合計件数: 8件」「合計金額(税込): 10,571,000円」があることを確かめてください。
2
書く
確定した一覧から1種類ずつ潰します。1回の依頼で1種類だけです。「@exercises/day2/LegacyReportService.java の意味のない変数名を、分かる名前に変えてください。d.get("qty") のようなカッコの中の文字は絶対に変えないでください。処理の中身も変えないでください。」のように、やらないことを2つ添えます。
3
実行
1種類潰すたびに比べます。java -Dfile.encoding=UTF-8 exercises/day2/LegacyReportService.java > after.txt を打ち、diff before.txt after.txt で比較します。何も表示されなければ成功です。行が出たら、そこが壊した場所なので直前の変更に戻ります。
4
くり返す
潰す、比べる、を残り時間まで繰り返します。潰しやすい順は、変数名 → マジックナンバー → 文字列連結 → ネスト → 重複です。生の Map をクラスに置き換えるのが一番大きいので、残り20分を切ったら手を出さないでください。
考えること 出力が同じなら中身を読まなくてよい、と言えるのはなぜか。逆に、出力が同じでも安心できないのはどういうときか。
AI の出方 一度に「全部きれいにして」と頼むと、まず出力が変わります。特に文字列連結は87箇所あるので、まとめて StringBuilder に替えさせると改行やスペースがどこかで落ちます。1種類ずつ、できれば1メソッドずつです。
いちばん多い事故が、変数名を変えるときに d.get("qty") のカッコの中まで書き換えることです。これは変数ではなくデータの取り出しキーなので、変えると別のものを探しにいって実行時に落ちます。落ちたらエラーをそのまま貼って「カッコの中の文字は元に戻してください」で直ります。
D2-3+ 8種類のうち最後に残る Map<String, Object> を、OrderRecord というクラスに置き換えてください。影響範囲が3メソッド全部に及ぶので、Shift+Tab で Planモードに入り、計画を先に出させます。
順番が要ります。OrderRecord の定義と addOrder だけを先に通して diff を取り、そのあとレポートのメソッドを1つずつ移して、毎回 diff を取ります。まとめてやると、差が出たときに原因が3箇所に散ります。
ここで分かるのは、影響範囲が広い変更ほど段階を細かく切る必要がある、という点です。AI は一度に全部やろうとするので、段階を切るのは人の仕事になります。
くわしく(背景・詰まったときの対処) 仕込んであるコードスメールは次の8種類です。答え合わせに使ってください。
1. 長大メソッド。generateMonthlyReport が97行、generateCustomerSummary が91行、generateHighValueAlert が55行。集計と整形と出力を1つのメソッドでやっています。
2. ループ内の文字列連結。result = result + ... が87箇所。文字列は変更できない型なので、つなぐたびに新しい文字列が作られます。件数が増えると効いてきます。
3. マジックナンバー。税率の 1.1 が3箇所、高額の閾値 1000000 が1箇所、緊急の閾値 2000000 が2箇所。数字を見ても何の数字か分かりません。
4. 深いネスト。最大7階層。262行目から290行目あたりが特にひどく、条件を追うのが難しくなっています。
5. 重複したロジック。ステータスを判定する同じ形の分岐が84行目、179行目、261行目の3箇所にあります。1つ直したら3つとも直す必要があります。
6. 意味のない変数名。cnt、tmp、d、q、p、val、st、dt など12種類。名前から中身が分かりません。
7. 生の Map で持っている。Map<String, Object> が5箇所。取り出すたびにキャストが要り、キーを打ち間違えても気づけません。
8. 引数を使っていない。generateCustomerSummary は顧客名だけで絞り込んでいて、月の絞り込みがありません。
全部潰す必要はありません。35分で何種類まで行けるかです。1〜6は比較的安全で、7は影響範囲が広く、8は仕様の判断が要ります。8に手を出すと出力が変わるので、今日は触らないでください。