綴Photo のコードレビューで、「マジックストリング」という指摘を受けた。
指摘自体は前から知っていたつもりだったけど、今回いちばん勉強になったのは 「指摘されたこと=全部やるべき、ではない」 という判断の方だった。マジックストリングは段階的に直せます。そして、どこで止めるかは指摘とは別の判断になります。
その「段階」と「止めどき」を、AI(Claude Code)と対話しながら整理した記録を残す。
この記事の前提: 指摘は AI、概念整理も AI との対話
正直に書いておくと、マジックストリングを指摘したのは Codex(第三者レビュー用に回している別の AI)で、「文字列・定数・enum の3段階」を整理してくれたのは Claude Code との対話だった。
途中で「つまりこういうこと?」と自分なりにまとめ直したら、3点ほど認識がズレていて訂正された。その訂正も含めて、進行形の学習ログとして書く。
直し方は3段階ある
段階2: 定数にする(綴Photo は既にここまで来ていた)
abstract class PeriodVisitStatusValue {
static const visited = 'visited';
static const completed = 'completed';
}
// 使う側
if (status == PeriodVisitStatusValue.completed) { ... }PeriodVisitStatusValue.completed のように 名前付きの定数 にしておくと、PeriodVisitStatusValue.completd とタイポした瞬間に「そんな定数はない」とコンパイラ(書いたコードを実行できる形に変換する前に、文法や名前の取り違えを下読みしてくれる校正係)が弾いてくれます。冒頭の Callout で書いた「書いてる時点では誰も言ってくれない」の "誰か" が、定数化によってようやく登場します。
実は綴Photo のコードは、レビュー時点で既にここまで来ていた。コードを grep してみたら、生の 'visited' / 'completed' はもう残っていなくて、全部この定数経由で書かれていた。つまり 「マジックストリングを直せ」という指摘の実害部分(タイポ事故)は、すでに潰れていた。
段階3: 本物の enum にする
ここから先がレビューの「残り」だった。
enum PeriodVisitStatusEnum { visited, completed }段階2では status の型は依然 String で、内部で status を String のまま受け取る関数(綴Photo だと _upsertStatus(int year, int month, String status))は技術的には 'バナナ' でも受け取れてしまいます。
おまけに switch (status) を書いたとき、全パターンを網羅しているかコンパイラがチェックしてくれます。将来 archived のような3つ目を足したら、対応漏れの switch を全部指摘してくれます。
自分なりにこう理解した(そして3点ズレていた)
ここまで聞いて、自分の言葉でこうまとめ直した。
文字列で判定してた → タイポすると条件が一致しなくなる → 変数として定義してタイポを無くした(ここまで完了)。enum という型を使えばリスト保管的になって応用が利く。でもそうすると DB が複雑になる。種類が多い時に検討すればいい。
大筋は合っていたけど、3点ズレていて訂正された。この訂正が地味に勉強になったので残す。
ズレ1: 「変数」じゃなくて「定数」 値が変わらないから「定数」。変数(後で書き換わる入れ物)だとタイポ防止の意味が薄れます。細かいけど、コンパイラが「その名前を知ってるか」で守ってくれている、という仕組みの理解に関わります。
ズレ2: enum は「リスト保管」「応用が利く」ではありません enum の核心は「決まった選択肢を1つの型に固める」こと。リスト(中身が増減する入れ物)とは別物だった。価値は「型システムが不正値を締め出す」「switch の網羅チェック」の2点で、"応用" という曖昧な言葉で捉えていたのがズレていた。
ズレ3: 「DB が複雑になる」じゃなくて「変換層が1枚増える」
自分のまとめは8割合っていて2割ズレていた。ズレを潰しておかないと、次に似た判断をするとき間違えます。
で、段階3はやらないことにした
ここが今回いちばん残したい判断。
段階3(enum + TypeConverter)には、ちゃんとメリットがあります。型で不正値を完全に締め出せるし、switch の網羅チェックも効きます。でも、やめた。理由はコスト対効果。
- 段階2(定数化)で、実害のあるリスク = タイポ事故はもう潰れています
- 段階3で増える「型強制」「switch 網羅チェック」は、値が2つ・使用箇所も数ファイルで・将来増える気配もない この状況だと効果が薄いです
- 一方コストは、Drift の自動生成コードを
build_runnerで作り直して、statusをString前提で扱っていた全箇所を enum 型に直して、それが噛み合うか検証する、という中規模の作業
「軽い掃除」のつもりで着手して、ふたを開けたら中規模だった。そして、その中規模をやり切っても得られる安全性の上乗せは薄いです。なので 「指摘は受けたが、ここでは段階2で十分」 と判断して、進捗メモには「status の値が増えたら enum 化を再検討」とだけ書いて閉じた。
段階3が「やる価値あり」になる条件
逆に、こういう時は enum + TypeConverter までやる価値が出る、という整理もできた。
- enum の値が多いです(5個も10個もある)
- 使用箇所が多くて、あちこちで
switch分岐しています - 値が将来増えそうです(増えた時の対応漏れが怖いです)
- DB への不正値混入が現実的なリスク
PeriodVisitStatus はどれにも当てはまらなかった。だから止めた。
学んだこと
- マジックストリング対策には段階があります。一気に最後までやるのが「正しい」わけじゃありません
- コードレビューの指摘は「方向」であって「ゴール」ではありません。「マジックストリングを直せ」の実害部分は段階2で潰れていて、残りは「やってもいいが、ここでは要らない」だった。指摘を全部消化しようとすると、効果の薄い作業に時間を吸われます
- 「軽い掃除」と思って着手したら中規模だった、はよく起きます。着手してスコープが見えた時点で「これは今やる価値があるか」を判断し直していいです
- 非エンジニアが AI に実装を任せていると、概念の理解は「8割合って2割ズレてる」くらいの精度になりがちです。自分なりにまとめ直して、ズレを訂正してもらう プロセスを挟むと、次の判断の精度が上がります
おわりに
今回いちばん効いたのは、技術的な知識そのものより「指摘を全部やらなくていい」という感覚だった。
レビューで指摘がたくさん出ると、つい全部消化したくなります。でも、指摘ごとに「実害はどこまで残っているか」「直しきるコストはいくらか」を見ると、段階2で止めていいもの・段階3まで必要なものが分かれます。その仕分けができると、限られた時間を効果の大きいところに使えます。
この記事は2026年5月時点の自分の理解で書いています。仕様や認識が変わってる可能性があります。