【ITニュース解説】Finding Duplicate Functions by Executing Them, Not Reading Them
2026年09月28日に「Dev.to」が公開したITニュース「Finding Duplicate Functions by Executing Them, Not Reading Them」について初心者にもわかりやすく解説しています。
ITニュース概要
「assay」は、コードを読まずに関数を実行し、入力に対する出力結果を比較して重複する関数を検出するツールだ。関数名やテキストではなく、実際の動作で同じ機能を持つ関数を効率的に特定し、コードの重複を防ぐ。
ITニュース解説
「assay」は、すでにテストを通過しているコードの品質をさらに高めることを目的としたツールである。特に、開発者が気づきにくい重複した機能(関数)を見つけ出すことに焦点を当てている。従来の重複検出方法では、コードのテキストを比較したり、関数名を比較したりする方法が一般的だったが、これらの方法には限界があった。例えば、同じ処理をしていてもコードの書き方が少し違ったり、名前が全く異なったりすると、重複として検出されなかった。
assayは、この限界を乗り越えるため、「コードを読む」のではなく「コードを実行する」ことで重複を検出するという画期的なアプローチを採用している。具体的には、検出対象となる各関数に対して、あらかじめ用意された一連の固定された入力値(これを「プローブ値」と呼ぶ)を順番に与えて実行する。そして、それぞれの入力値に対して関数が何を返したか、あるいはどのようなエラーを発生させたか、という「実行結果の履歴」を記録する。この記録を「アウトカムベクトル」と呼ぶ。
関数を実行してアウトカムベクトルを生成した後、assayはすべてのアウトカムベクトルを比較する。もし二つの関数が全く同じアウトカムベクトルを持っていたら、それらの関数は「同じ処理をしている可能性が高い重複候補」とみなされる。この時、実行結果の履歴(アウトカムベクトル)が同じ関数同士を一つのグループにまとめる仕組みを使うことで、すべての関数の組み合わせを一つずつ比較するよりも効率的に重複候補を見つけ出すことができる。つまり、テキストや関数名ではなく、実際にコードが何をするのかという「振る舞い」によって重複を判断する。
このアプローチの利点は、従来のテキストや名前による検出では見つけられなかった重複を発見できる点にある。例えば、記事中ではis_wordyと_wordという二つの関数が例として挙げられている。これらは名前もコードも異なっていたため、通常のツールでは重複とみなされなかったが、assayは実行結果が一致したため、重複候補として検出した。実際には、これらの関数は一部の条件(英数字であるかの判断基準)がわずかに異なるだけで、ほとんど同じ処理をしていた。
しかし、実行結果による判断には注意が必要である。assayは「異なる」という結論と「同じ」という結論を非対称に扱っている。「異なる」(differs)と判断された場合、それは特定の入力値によって関数が異なる振る舞いをしたという「証拠」(witness)が存在することを意味する。この証拠は誰でも再現可能なため、differsの結論は「証明」に近い確かなものである。一方、「同じ」(same)と判断された場合、それはあくまで「与えられた有限の入力値の範囲では、異なる振る舞いは見つからなかった」ということを意味する。これは「本当に全く同じである」という強い主張ではなく、「証拠が見つからなかった」という弱い主張である。
そのため、入力値(プローブ値のリスト)の質が極めて重要になる。例えば、is_wordyと_wordの例では、初期の入力値では両者が同じ振る舞いをしたが、特殊な文字(例えば半角文字「½」)を追加したところ、異なる振る舞いをすることが判明し、「異なる」という結論に変わった。このことから、sameと判断された場合でも、より網羅的な入力値を追加することで、実は異なる部分が見つかる可能性があることを示している。assayは、利用者に「あなたの入力はどのような文字を含んでいないか、そしてそれらを追加すべきではないか」と問いかけることの重要性を教えてくれる。
assayは、誤った重複検出を避けるための「ガード」の仕組みを多数備えている。これは、開発者が過去に陥った誤りを教訓として組み込まれたものである。例えば、すべての入力でTypeErrorを発生させる二つの関数や、常に同じ定数を返す二つの関数は、見かけ上は同じアウトカムベクトルを持つため、「同じ」と誤判定される可能性がある。また、単に引数をそのまま返すだけの関数も、与えられた入力に対して同じ出力となるため、これも無関係な関数が「同じ」と誤判定される原因となる。これらのケースでは、アウトカムベクトルの数が少なすぎたり、入力に対する振る舞いが表面的にしか一致していなかったりする。assayは、このような「中身のない一致」を検出するために、複数のガードを設けている。例えば、単に引数をコピーするだけの関数も重複とはみなされないように設計されている。
さらに、一つの関数が複数の名前で参照されるケース(例えば、CommonJSモジュールのエクスポートや、依存するモジュールをそのまま再エクスポートするバレルモジュール)にも対応している。これらのケースでは、実体は一つの同じオブジェクトであるため、名前やソースの比較ではなく、オブジェクトそのものの同一性によって「同じもの」として扱われる。
assayが比較対象とする関数には厳格な基準がある。対象となるのは、モジュールレベルで定義され、特別な装飾(デコレーター)がなく、メソッドやジェネレーターではない関数に限られる。また、引数は1個から3個までで、ファイルアクセス、ネットワーク通信、時刻の取得、乱数生成といった外部依存を持たないことが条件となる。これらの厳しい制約により、実際に比較される関数は全体の約10%程度に限定されるが、これはツールの信頼性と再現性を確保するために意図的に設けられた設計方針である。比較されなかった関数についても、なぜ比較対象外になったのかを明確に報告することで、「何も見つからなかった」という報告が「何も調べなかった」と誤解されることを防いでいる。
assayは多言語にも対応しており、PythonとJavaScriptのコードを同じ仕組みで検査できる。共通の入力データ(ラダー)はJSON形式で扱われ、両言語で同じ入力が与えられる。実行結果も、両言語の異なるエラー分類を考慮しつつ、共通のJSON形式で記録される。これにより、ある言語では例外が発生し、別の言語では正常な値が返されるといったケースも正確に捉え、それが「異なる」ことの強力な証拠となり得る。
このツールの背後には、PythonとJavaScriptそれぞれに対する膨大な数のテストと、ミューテーションテストと呼ばれる手法が導入されている。ミューテーションテストは、意図的にコードに「破壊」を加えて、それがテストによって検出されるかを検証する手法である。これにより、過去に実際に発生したバグ(例えば、テンプレートリテラル内のヌルバイトが、表示上はスペースに見えても実行時には異なる振る舞いをしていたケース)を再現し、その修正が後退しないことを保証している。また、あるガード(誤検出防止の仕組み)を削除しても観測される振る舞いが変わらなかった例では、そのガード自体が「デッドコード」(不要なコード)であることを発見し、ツールの自己診断能力をも示している。
このように、assayは「実行」というアプローチで重複関数を検出するだけでなく、その検出の信頼性を高めるための徹底した設計思想と品質保証プロセスに基づいて構築されている。これにより、開発者は、今まで気づかなかった潜在的なコードの重複を見つけ出し、より堅牢で保守しやすいシステムを構築できる可能性を高める。