非推奨関数のすべての呼び出し元を検索する

非推奨関数を削除する前に、その関数の呼び出し元をすべて見つける方法

コードベースから非推奨関数を削除することは、概念的には開発者ができる最も簡単な作業の1つです。定義を削除し、誰も使用していないことを確認してコミットするだけです。しかし実際には、非推奨になるほど長く存在してきた関数については、「誰も使用していないことを確認する」というステップで問題が発生します。その関数は、チームに所属していない人が何年も前に書いたコード、変更頻度の低いリポジトリ、現在のチームが所有していない言語やフレームワークから呼び出されている可能性があります。ラッパー、リフレクション、または静的呼び出しグラフには現れないランタイムディスパッチメカニズムを介して間接的に呼び出されている可能性もあります。生成されたコード、テストスキャフォールディング、または名前でトリガーする構成ファイルで参照されている可能性もあります。非推奨としてマークした開発者も、最終的に削除した開発者も、リポジトリを横断した完全な呼び出し元インベントリを作成できるツールがなければ、これらのことを知る術がないかもしれません。

何かを削除する前に、すべての発信者を見つけてください

SMART TS XL 変更を加える前に、あらゆる関数のすべての呼び出し元を特定する、言語を跨いだ呼び出しグラフを構築します。

詳細

これを誤ると、即座に具体的な代償を払うことになります。呼び出し元の検出が不完全なまま関数が削除されると、その関数に依存しているシステムで実行時障害が発生します。単一のデプロイメントを持つモノリスでは、障害発生範囲は限定されます。しかし、複数のサービスがそれぞれ独立してデプロイされている分散システムでは、障害が連鎖的に発生します。関数を提供していたサービスが更新されても、その関数を利用するサービスは更新されず、異なるチームが所有する可能性のある本番環境のシステムで、実行時に障害が発生するのです。COBOLプログラムが共有ユーティリティパラグラフを名前で呼び出すメインフレーム環境では、特定のバッチジョブが実行されるまで障害が顕在化しない可能性があり、そのバッチジョブは週単位または月単位である場合もあるため、通常のテストサイクルでは参照の欠落が見過ごされてしまいます。非推奨コードの管理というより広い文脈で検討すると、リスクは時間とともに増大します。不完全に削除された非推奨コードは、そのまま残された非推奨コードよりも危険です。なぜなら、削除によって完全性の錯覚が生じ、残りの呼び出し元はもはや存在しない定義に対して実行され続けるからです。

この記事は、関数削除前に呼び出し元を特定するための実践的なガイドです。完全な呼び出し元インベントリに必要なもの、開発者が最初に頼るツールが構造的に不十分な理由、異なる種類の呼び出し関係には異なる分析アプローチが必要であること、そして言語、プラットフォーム、リポジトリが混在するエンタープライズ規模のコードベースにおける真のクロスシステム呼び出し元列挙とはどのようなものかについて説明します。

発信者特定が見た目より難しい理由

呼び出し元検出の表面的な方法は、すべての開発者にとって馴染み深いものです。IDEで関数名を右クリックし、「すべての参照を検索」または「呼び出し階層を表示」を選択して結果を確認します。これは、単一のIDEインスタンスに読み込まれた単一のプロジェクトの範囲内では確実に機能します。しかし、コードベースがその範囲を超えると、出力には表示されない形で結果が不完全になります。IDEは、それらを含むリポジトリをインデックス化していないため、どの呼び出し元が見つからなかったかを示しません。開発者は、完全に見える結果セットを見て、それに従って作業を進めます。

大規模な呼び出し元検出における構造的な問題は、開発者が最も使い慣れているツールはインデックスの範囲によって制限されており、大規模で分散された多言語システムでは、その範囲は特定の関数が呼び出される可能性のある場所のごく一部しかカバーしていないことです。開発者が結果の完全性に抱く信頼は、実際の検索の完全性と反比例します。小規模な単一言語のコードベースでは、IDEの呼び出し階層は確かに信頼できます。しかし、複数のリポジトリ、言語、および展開環境にまたがるエンタープライズシステムでは、体系的に誤解を招く可能性があります。コードのエントロピーとリファクタリングのリスクの観点から分析すると、レガシーモジュールは非推奨のインターフェースに依存している可能性があり、新しいサービスは以前の環境向けに設計されたルーチンを呼び出している場合があります。そして、これらのシステム間の呼び出し関係こそ、IDEに限定された検索では捉えられないものなのです。

呼び出し元検出が失敗する具体的な理由を理解するには、主要な呼び出しタイプ(直接呼び出し、間接呼び出し、言語間呼び出し、動的ディスパッチ)をそれぞれ検証する必要があります。それぞれ失敗の原因が異なり、正しく解決するには異なる分析手法が必要です。

リポジトリ境界を越えた直接呼び出し

直接呼び出しは最も単純な呼び出しタイプで、ある関数が別の関数を名前で明示的に呼び出します。単一のリポジトリ内であれば、IDEはこれらの呼び出しを確実に処理します。しかし、リポジトリの境界を越えると、IDEのインデックスが境界をまたいでいないため、解析は失敗します。リポジトリAが共有ユーティリティ関数を定義し、リポジトリB、C、Dがそれぞれそれをインポートして呼び出している場合、これらのリポジトリのいずれかのIDEは、自身のインデックス付きスコープ内の呼び出ししか認識しません。

このマルチリポジトリ呼び出しパターンは、共有ライブラリがパッケージとして公開され、数十のサービスで利用されるマイクロサービスアーキテクチャでは例外ではなく標準となっています。共有パッケージ内の関数を非推奨にするライブラリのメンテナーは、どのサービスがまだその関数を呼び出しているかを知る必要があります。しかし、IDEはコンシューマーについて何も知りません。パッケージマネージャはどのサービスがパッケージに依存しているかは知っていますが、各サービスがパッケージ内のどの特定の関数を呼び出しているかは知りません。「このサービスはこのパッケージのバージョンXを使用している」から「このサービスはこの特定の非推奨関数を呼び出す」へのマッピングには、すべてのコンシューマーのソースコードをインデックス化し、呼び出しを特定の関数定義に解決する必要があります。

間接呼び出し:ラッパー、デリゲート、ファサード

関数は、その利用者によって直接呼び出されるとは限りません。追加のログ記録、エラー処理、またはパラメータ変換を提供するラッパー関数を介して呼び出される場合があります。デリゲートまたは関数ポインタに割り当てられ、デリゲートを介して呼び出される場合もあります。サービスレジストリまたはプラグインフレームワークに登録され、ディスパッチメカニズムを介して名前で呼び出される場合もあります。これらのいずれの場合も、非推奨関数への呼び出しを直接検索しても、実際の呼び出し元は非推奨関数自体ではなく、ラッパーまたはディスパッチャを呼び出すため、結果は不完全になります。

ラッパーを介した呼び出しは、ログ記録、認証、再試行ロジックなどの横断的な関心事がラッパーパターンによってコア関数の周りに階層化されている大規模なコードベースで特に一般的です。ログ記録ユーティリティによってラップされた非推奨関数は、非推奨関数の名前を含むコードではなく、ラッパーのすべての呼び出し元によって実質的に呼び出されます。これらの呼び出し元を特定するには、ラッパーをトレースする必要があります。ログ記録ユーティリティが非推奨関数を呼び出すため、ログ記録ユーティリティのすべての呼び出し元は、非推奨関数の間接的な呼び出し元となります。この再帰的な呼び出しグラフの走査こそが、徹底的な呼び出し元インベントリと表面的な参照検索を区別するものです。

委譲レイヤーを介して非推奨メソッドにアクセスするJavaの代表的な例を考えてみましょう。

ジャワ

// Deprecated in the core service
@Deprecated
public BillingResult calculateLegacyFee(Account account) {
    // original implementation
}

// Facade that delegates; not visible as a direct caller in a simple reference search
public BillingResult computeFee(Account account) {
    return calculateLegacyFee(account);  // indirect caller
}

// Actual consumer; calls computeFee, unaware of the underlying deprecated method
public void processMonthlyBilling(List<Account> accounts) {
    accounts.forEach(a -> computeFee(a));  // two hops from the deprecated function
}

「すべての参照を検索」検索 calculateLegacyFee 収益 computeFee 唯一の呼び出し元として。返されません processMonthlyBillingこれは、非推奨の動作の真の消費者です。完全な呼び出し元インベントリには、コールグラフを上流にたどる必要があります。 computeFee 最終的に非推奨メソッドを呼び出すすべてのパスを特定する。

言語を跨いだ呼び出し

言語間呼び出しは、標準的な呼び出し元検出ツールが最も完全に機能しなくなるカテゴリです。Javaサービスがミドルウェア層を介して名前でCOBOLプログラムを呼び出す場合、Pythonスクリプトが非推奨関数をラップするストアドプロシージャを呼び出す場合、またはJCLジョブが内部で非推奨の段落を呼び出すプログラムをPROGNAMEで呼び出す場合、これらの関係はどの単一言語の呼び出しグラフにも表示されません。各言語のツールは、呼び出しの自言語側しか認識できないのです。

メインフレーム環境では、言語間呼び出しは構造的かつ広範囲にわたります。JCLジョブストリームは、実行するCOBOLプログラムの名前を指定します。COBOLプログラムは、名前で段落とサブルーチンを呼び出します。コピーライブラリで定義されたユーティリティ段落は、多くのプログラムで共有されます。コピーライブラリ内の段落が非推奨になった場合、すべての呼び出し元を見つけるには、COBOL呼び出し関係(どのプログラムがコピーライブラリを含み、どのプログラムが段落を呼び出すか)、JCL呼び出し関係(どのジョブがそれらのプログラムを呼び出すか)、および言語間インターフェース(それらのプログラムとやり取りするJavaまたはSQL)を理解する必要があります。これらの関係すべてを網羅する単一のツールはありません。レガシーシステムの静的解析の分析で検討したように、呼び出し関係がJCL、COBOL、およびシステム間インターフェースに同時に及ぶ場合、最新の環境向けに構築された静的解析ツールでは、レガシープログラムがどのようにトリガーされ、呼び出され、相互接続されているかの全体像を把握することはできません。

動的ディスパッチとリフレクション

呼び出し元によっては、ソースコード内の関数名ではなく、実行時に関数を解決するメカニズム(Java や .NET のリフレクションなど)を介して関数を呼び出す場合があります。 getattr/__call__ Python では、COBOL の遅延バインディングは CALL identifierポリモーフィズムによる動的ディスパッチ、またはプラグインフレームワークや設定駆動型システムにおける文字列による呼び出しなど。これらの呼び出し元には、静的解析で確実に検出できる形式で非推奨関数の名前は含まれていません。

実行時にロードされ、リフレクションを介して関数を呼び出すために使用される、関数名を文字列として指定する構成ファイルは、ソースコード解析ではどこにも現れない呼び出し元です。インターフェースによって登録されたハンドラを検出して呼び出すプラグインフレームワークは、呼び出しグラフではディスパッチメカニズムへの呼び出しとしてのみ現れ、特定のハンドラへの呼び出しとしては現れません。これらの呼び出し元を特定するには、動的なディスパッチパターンを見つけるための静的解析、実際の呼び出しを観察するための実行時トレース、およびどの関数がディスパッチされるかを決定する構成と登録ロジックの手動検査を組み合わせる必要があります。難読化および生成されたコードに対する静的解析の検討で説明したように、実行パスがソースコードで直接表現されていない場合、静的解析は直接的なテキスト参照ではなく構造パターンから可能性のあるパスを再構築する必要があり、これらの再構築にはディスパッチメカニズム自体の言語認識解析が必要です。

開発者が最初に手に取るツールと、そこで止まる場所

開発者が呼び出し元を特定しようとする際に使用するツールには、予測可能な順序があり、それぞれのツールが信頼できる結果を提供できなくなる時点も同様に予測可能です。各ツールの出力は実際には完全ではない場合でも、完全に見えることがあるため、この順序を理解することが重要です。

IDE呼び出し階層:単一プロジェクト内での信頼性

IDEの呼び出し階層機能は、最も自然な最初のステップです。IntelliJ IDEA、Visual Studio、VS Code、Eclipseはいずれも、現在のプロジェクトまたはワークスペースのインデックス付きスコープ内で、選択した関数の呼び出し元を再帰的に列挙する「すべての呼び出し元を検索」または「呼び出し階層を表示」といった機能を提供しています。単一のリポジトリと単一の言語内でのみ使用される関数であれば、これらの機能は正確かつ十分です。

制限事項はスコープに明記されています。「インデックス化されたスコープ内」です。他のリポジトリ、他の言語ランタイム、または直接プロジェクト参照ではなくパッケージマネージャを介してこのコードに依存するサービス内の呼び出し元は、スコープ外です。IDE は検索しなかったものを示しません。開発者は結果セットを受け取りますが、インデックス化されなかった追加のリポジトリがいくつ存在するか、それらのリポジトリのうちいくつが非推奨の関数を使用しているか、あるいは「呼び出し元ゼロ」という結果が本当に呼び出し元ゼロを意味するのか、「このツールが認識できるシステム内の呼び出し元ゼロ」を意味するのかを知ることはできません。

grepとテキスト検索:広範囲だが構造を考慮しない

IDE の検索が不完全であると疑われる場合、次のステップは通常テキスト検索です。利用可能なソースディレクトリを grep で検索するか、GitHub または GitLab のコード検索を使用してプラットフォーム検索を実行します。これにより範囲が大幅に広がり、アクセス可能なリポジトリであれば、他のリポジトリの呼び出し元も見つけることができます。構造的な問題は、テキスト検索が呼び出しではなく文字列を検索することです。関数名のすべての出現箇所が返され、関数に言及するコメント、ドキュメント文字列、デバッグ目的で関数名を指定するログ メッセージ、関数名を含むが呼び出していない文字列リテラルなども含まれます。また、関数名が検索文字列と異なる呼び出し元も見逃します。エイリアスによる呼び出し元、名前が省略される可能性がある COBOL の部分一致名による呼び出し元、実行時に名前が組み立てられる動的呼び出しによる呼び出し元などです。

テキスト検索の結果セットでは、実際の呼び出し元、ドキュメント参照、文字列の衝突による誤検出を判別するために、手動でフィルタリングを行う必要があります。大規模なシステムでは、このフィルタリング自体がかなりの労力を要するだけでなく、フィルタリング処理によって完全性を検証することもできません。例えば、呼び出し元が異なる名前を使用しているために見落とされた場合、フィルタリングされた結果セットにはその見落としを示す情報が一切含まれません。

コンパイラ警告と @Deprecated 注釈

現代の言語とツールチェーンは、非推奨の関数が呼び出されたときに警告を生成する非推奨注釈メカニズムを提供します。Java の @Deprecated アノテーションと -Xlint:deprecation 呼び出し箇所でコンパイル時警告を生成します。C# の [Obsolete] 属性はビルド時に警告を生成します。Go の非推奨関数に名前を付け、godoc にドキュメント化する慣習では、警告は自動的に生成されません。これらのメカニズムは有用ですが、特定の制限があります。つまり、非推奨が注釈付けされているコードベースと同じコードベースに対してコンパイルする呼び出し元でのみ機能します。

ライブラリの古いバージョンを使用する呼び出し元、 @Deprecated アノテーションを使用する呼び出し元は警告を受け取りません。ソースからコンパイルするのではなくバイナリ成果物を使用する呼び出し元も警告を受け取りません。異なる言語でクロス言語インターフェースを介して呼び出しを行う呼び出し元も警告を受け取りません。そして重要なことに、コンパイル中に生成される警告はコンパイラの視点に限定されます。つまり、コンパイラが認識する呼び出しについて警告するものであり、別々にコンパイルされる他のリポジトリの呼び出しについては警告しません。マルチサービスシステムで呼び出し元検出の唯一のメカニズムとしてコンパイラ警告を使用すると、マイクロサービスアーキテクチャで通常発生する、独立してコンパイルされるすべての呼び出し元を見逃すことになります。

静的解析ツール:より優れているが、適用範囲は限られている

専用の静的解析ツールは、対象言語のIDEよりも正確な呼び出し元列挙を提供し、複数のコードベースをインデックス化するように設定すれば、リポジトリの境界を越えて処理できる場合も多い。テキストマッチングに頼るのではなく、適切な呼び出しグラフを構築し、エイリアシングや間接呼び出しをIDEの検索よりも適切に処理する。また、CIパイプラインで実行することで、追加された新しい呼び出し元を検出できる。これらは、単一言語向けの最も優れたアプローチと言える。

制限は、IDE を制約するのと同じスコープ境界ですが、今度はツールレベルで発生します。Java 静的解析ツールは COBOL プログラムをインデックス化せず、COBOL アナライザーは Java サービスをインデックス化せず、どちらも JCL ジョブ ストリームをインデックス化しません。非推奨関数が、JCL ジョブから呼び出され、そのデータ出力が Java サービスによって消費される COBOL プログラムから呼び出される COBOL ユーティリティであるシステムでは、各静的解析ツールは呼び出し関係の断片しか認識できません。基本的なリファクタリング手法の文脈で検討されているように、まれなエラー条件やフォールバック ブランチを含め、特定のコード セクションへのすべての実行パスを特定するには、単一言語ツールでは言語境界を越えて構築できないような、完全な呼び出しグラフ マッピングが必要です。

完全な発信者インベントリに実際に必要なもの

企業システムにおける非推奨関数の完全な呼び出し元インベントリは、検索結果ではありません。それは、直接パス、間接パス、言語間パス、動的ディスパッチパスなど、非推奨関数に到達できるすべての実行パスを構造的に列挙したものです。この列挙を作成するには、単一の標準ツールでは提供されない複数の機能が必要です。

統一された言語間呼び出しグラフ。呼び出しグラフは、システム内のすべての言語を網羅する必要があります。JCLプロシージャからCOBOLプログラムへの呼び出し、COBOLプログラムから共有ユーティリティパラグラフへの呼び出し、およびミドルウェアインターフェースを介してJavaサービスから同じCOBOLプログラムへの呼び出しは、すべて同じグラフ内のノードとエッジである必要があります。非推奨関数はこのグラフ内のノードであり、呼び出し元列挙は、どの言語から発生したかに関わらず、直接および推移的なすべての受信エッジをたどったものです。

呼び出しグラフ全体を再帰的に走査します。直接呼び出し元は最初のレイヤーにすぎません。完全なインベントリを作成するには、呼び出しグラフを間接呼び出し元、ラッパー関数、ファサードレイヤーを経て上流に辿り、呼び出し元を持たない関数(呼び出しチェーンの真のエントリポイント)に到達するまで走査する必要があります。非推奨関数で終わるパス内のすべての関数は、関連する意味で呼び出し元です。非推奨関数を削除すると、それを通過するすべてのパスが壊れます。

リポジトリ間インデックス作成。呼び出しグラフには、共有ライブラリやパッケージを介して関数に依存するリポジトリを含め、関数を呼び出す可能性のあるすべてのリポジトリのコードを含める必要があります。そのためには、すべてのリポジトリを同時にインデックス化し、リポジトリ間のインポート関係を解決して、あるリポジトリの呼び出しを別のリポジトリの定義に接続する必要があります。

間接呼び出しパターンの検出。分析では、リフレクション、動的ディスパッチ、関数ポインタ、デリゲート、および設定ファイル内の文字列ベースの呼び出しを介して行われる呼び出しを特定する必要があります。これらは、直接的な呼び出しエッジ解決ではなく、パターンベースの検出を必要とします。つまり、コード内の動的ディスパッチメカニズムを見つけ、どのような条件下でどの関数にディスパッチできるかを判断する必要があります。

アクティブな呼び出し元とテスト専用またはデッドな呼び出し元を区別します。すべての呼び出し元に対して同じ対応が必要なわけではありません。非推奨関数自体のテスト フィクスチャにのみ存在する呼び出し元は、クリーンアップの一部として削除する必要があり、移行する必要はありません。使用状況分析によってデッド コードとして識別されたコード内の呼び出し元は、関数の削除を妨げるものではありません。これらの区別を理解するには、呼び出し元の列挙と、実際にアクティブなコード パスに関する情報を組み合わせる必要があります。静的分析によるデッド コード検出の検討で詳しく説明されているように、不完全なドキュメントや履歴依存関係に関する不確実性のために、ミッション クリティカル システムでは到達不能なコードや未使用の関数が何年も残ることがあり、非推奨関数の呼び出し元インベントリでは、アクティブな呼び出し元とデッドな呼び出し元を区別する必要があります。

廃止から削除までのプロセス:構造化されたアプローチ

非推奨関数の削除を、構造化されたプロセスではなく単一のイベントとして扱うことが、呼び出し元検出の失敗の主な原因です。正しいアプローチは、削除を、コードが削除されるずっと前から始まる多段階プロセスの最終段階として扱うことです。

フェーズ1:マーキングと測定

最初のステップは、言語の組み込みメカニズムを使用して関数を非推奨として注釈付けすることです(@Deprecated ジャワでは、 [Obsolete] C#では、 #[deprecated] Rust またはそれに相当する言語で)呼び出し元のベースライン数を確立します。このベースラインは単一の検索の結果ではなく、関数を呼び出す可能性のある既知のすべてのコードベースをインデックス化し、結果をカウントした結果です。ベースラインには 2 つの目的があります。移行範囲を定量化することと、呼び出し元の移行の進捗状況を測定するための基準を提供することです。

ベースラインは、発信者の種類と場所ごとに整理する必要があります。

発信者カテゴリ数量カウント優先オーナー
同じリポジトリ内の直接呼び出し元Nハイ現在のチーム
依存サービスへの電話対応Nハイサービスオーナー
ラッパー関数を介して呼び出しN技法ラッパーオーナー
生成されたコードまたはフレームワークコード内の呼び出し元N技法フレームワークチーム
テスト専用コード内の呼び出し元Nロー現在のチーム
デッドコードでの発信N清掃のみ現在のチーム

フェーズ2:通知と移行

完全な呼び出し元インベントリがあれば、移行は事後対応ではなく組織的な取り組みになります。各呼び出し元の所有者には、具体的な呼び出し場所が通知されます。「この関数を呼び出している可能性があります」ではなく、「この関数は247行目で呼び出されています」というように通知されます。 BillingService.java82行目 AccountProcessor.java、そして統合テストの14行目では BillingServiceTest.javaこのレベルの具体性こそが、呼び出し元インベントリによって可能になるものであり、一般的な非推奨警告では提供できないものです。

通知と同時に移行パスを提供することが不可欠です。非推奨化には、代替関数のドキュメント、旧実装と新実装の動作の違いの説明、変更が複雑な場合は変更前と変更後のコード例を含める必要があります。他のチームのコードベースから呼び出しを行うユーザーに対しては、各チームに独自の優先順位と納期があるため、移行のタイムラインは一方的に発表するのではなく、明示的に協議する必要があります。依存システム全体にわたるデータベースのリファクタリングの文脈で検討されているように、古い構造を非推奨化する前に新しい構造の利用者を段階的に導入することが、破壊的変更が予期せぬインシデントとして現れるのを防ぐための規律です。

フェーズ3:発信者数の監視

ベースライン測定から計画された削除日までの間、呼び出し元数を継続的に監視する必要があります。呼び出し元が代替機能に移行するたびに、呼び出し元数は減少します。アクティブな呼び出し元数がゼロになった時点で削除ゲートに到達します(テスト専用およびデッドコードの呼び出し元は、機能自体と同時に削除される場合があります)。継続的な監視には、コードの変更によって古くなる一度限りのインベントリに頼るのではなく、CIパイプラインの一部としてスケジュールに基づいて呼び出し元列挙を実行する必要があります。

監視機能では、非推奨期間中に追加された新しい呼び出し元も検出します。大規模な組織では、開発者が非推奨化を認識していなかったり、コードレビューで見落とされたり、自動コード生成ツールが非推奨関数を呼び出すコードを生成したりといった理由で、移行期間中に非推奨関数を呼び出す新しいコードが作成されることがよくあります。新しい呼び出し箇所でエラーとなるように構成された、非推奨関数のCIレベルの呼び出し元検出機能により、移行中に呼び出し元が増加するのを防ぎます。

フェーズ4:削除前に完全性を確認する

関数を削除する直前に、既知のすべてのコードベース全体に対して呼び出し元列挙を最後にもう一度実行する必要があります。この最終チェックは安全ゲートとして機能し、アクティブな呼び出し元の呼び出し元数がゼロになったことを確認し、CI監視で検出されなかった後期の追加箇所を特定します。この時点で、インベントリは動的な呼び出し元(文字列で関数を参照する構成ファイル、リフレクションベースの登録、および初期分析で特定されたその他の間接的な呼び出しメカニズム)が存在しないことも検証する必要があります。

検証は、非推奨関数を公開している共有ライブラリまたはパッケージの依存関係グラフにまで及ぶ必要があります。関数が外部の関係者が利用する公開APIの一部である場合、削除スケジュールは、内部コード分析ではアクセスできない可能性のある外部利用者も考慮に入れる必要があります。内部システムの場合、検証はインデックス付けされたすべてのコードベースを対象とします。公開APIの場合、検証は既知の利用者セットに加え、外部利用者が移行しなければならない定義されたサンセット期間を対象とします。

レガシー環境とメインフレーム環境では、発信者検出の仕組みがどのように異なるのか

上記で述べた課題は、あらゆる大規模ソフトウェアシステムに当てはまりますが、メインフレームやレガシーシステムでは特に深刻です。なぜなら、これらの環境における呼び出し関係は、最新の呼び出し元検出ツールでは分析するように設計されていないメカニズムを通して表現されているからです。

COBOL環境では、関数はCALL文によって呼び出されます。CALL文では、リテラル文字列、プログラム名を含むデータ項目、またはプロシージャポインタによってターゲットを参照できます。リテラル文字列の場合は静的解析で解決できますが、データ項目の場合は、呼び出し時点でデータ項目がどのような値を持つかを判断するためにデータフロー解析が必要です。プロシージャポインタの場合は、ポインタがどのように割り当てられるかを追跡する必要があります。これらの呼び出しメカニズムはそれぞれソースコード内で異なる形で現れるため、解決には異なる解析が必要です。

JCL環境では、EXEC PGM=文でプログラム名を指定してプログラムを呼び出します。プログラム名は、ロードライブラリ内のコンパイル済みモジュールに対応する文字列です。JCLを通してCOBOLプログラムの呼び出し元を追跡するには、JCLを解析してプログラム名を抽出し、それらの名前を実装するコンパイル済みCOBOLプログラムにマッピングし、それらのプログラム内のどのCOBOL段落が非推奨ユーティリティを呼び出しているかを特定する必要があります。このような複数のステップからなる解決処理は、単独で動作するCOBOLアナライザーやJCLアナライザーのいずれのスコープにも完全に含まれません。

COBOL環境では、共有コピーブックが特に重要なケースとなります。コピーブックで定義された非推奨の段落は、COPY文を介して多くのプログラムにインクルードされる可能性があります。段落は各プログラムで物理的に複製されるのではなく、コンパイル時にインクルードされます。コピーブックのインクルードを解決せずにソースファイル内の段落名の出現回数をカウントする分析では、過剰カウント(コピーブック自体に段落定義が含まれているため)と過少カウント(コピーブックをインクルードするすべてのプログラムがその段落にアクセスできるという事実を見落とすため)の両方が発生します。呼び出し元を正しく検出するには、どのプログラムがどのコピーブックをインクルードし、それらのコピーブック内のどの段落を実際に呼び出しているかを理解する必要があります。ハードコードされた参照とその下流のコンシューマーとの関係は、構造変更を行う前にこれらのプログラムレベルの呼び出し関係を解決することがなぜ不可欠なのかを示しています。単純な文字列参照に見えるものが、数十のプログラムが重要な機能にアクセスするための唯一のメカニズムである可能性があるのです。

認定条件 SMART TS XL 完全な発信者インベントリを構築します

SMART TS XL インデックス付き環境内のすべての言語、プラットフォーム、リポジトリにわたる統一された呼び出しグラフを構築します。COBOL プログラム、JCL ジョブ ストリーム、Java サービス、.NET アプリケーション、SQL ストアド プロシージャ、Python スクリプト、およびその他のソース アーティファクトはすべて、言語固有の分析を使用して解析され、共通の相互参照グラフになります。各関数、段落、プロシージャ、メソッド、およびプログラム ユニットは、そのグラフのノードです。COBOL CALL ステートメント、Java メソッド呼び出し、JCL EXEC PGM、または SQL EXEC など、各呼び出し関係は、型付きエッジです。このグラフは、言語ごとの部分的なビューではなく、システムの完全な呼び出しトポロジを表します。

機能が削除対象としてマークされると、 SMART TS XLの呼び出し元列挙は、ターゲット関数ノードから呼び出しグラフを辿り、呼び出し階層の各レベルの呼び出し元を収集します。この走査は再帰的で、ラッパー関数、ファサード層、中間ユーティリティを経て、呼び出し元が存在しない関数に到達するまでグラフを辿ります。これらの関数は、呼び出しチェーンの真のエントリポイントを表します。結果は、言語、リポジトリ、呼び出し元タイプ、呼び出し深度ごとに整理され、直接呼び出し元と間接呼び出し元、アクティブな呼び出し元とデッドコード呼び出し元を区別する構造化されたインベントリをチームに提供します。

プラットフォームの影響分析機能は、これを構造化された変更影響レポートへと拡張します。つまり、どの関数が非推奨関数を呼び出しているかだけでなく、依存関係チェーンのあらゆるレベルでどのプログラム、サービス、バッチジョブ、およびJCLプロシージャが影響を受けるかを示します。このレポートは、非推奨から削除までのプロセスを実行可能にする成果物です。所有者を特定し、具体的な呼び出し場所を識別し、削除を安全に進める前に必要な移行の範囲を定量化します。エンタープライズ変更管理における影響分析の詳細で検討したように、構造変更を行う前に影響を受けるコンポーネントを列挙できる能力は、複雑で相互接続されたエンタープライズシステムを安全に運用するための基礎的な要件です。

SMART TS XL また、非推奨化プロセスの継続的な監視フェーズもサポートします。ソースコードの変更がインデックス化されるにつれて相互参照グラフが継続的に更新されるため、非推奨関数の呼び出し回数は常に最新の状態に保たれます。CIパイプラインとの統合により、非推奨関数への新規呼び出し時に自動チェックが失敗するように設定できるため、違反が事後的に発見されるのではなく、新規コードが導入された時点で移行規律が徹底されます。この初期列挙、移行ガイダンス、および継続的な監視の組み合わせにより、非推奨関数の注釈付けから安全な削除までのライフサイクル全体をカバーします。

後悔のない機能削除

スムーズに機能削除が完了するか、本番環境で障害が発生するかの違いは、ほぼ常に呼び出し元検出の完全性の違いにあります。削除自体は簡単です。定義を削除してデプロイするだけです。重要なのは準備段階であり、その準備の質は、基となる呼び出し元インベントリの精度に左右されます。

呼び出しグラフが浅く、単一言語で、単一のリポジトリ内に収まっているシステムでは、IDEの呼び出し階層とコンパイラの警告で十分な準備となります。しかし、呼び出しグラフが複数の言語、複数のリポジトリ、複数のプラットフォーム、そして場合によっては数十年にわたるコードにまたがるシステムでは、これらのツールは実際の呼び出し元の表面積のごく一部しかカバーしておらず、その全容を把握することは困難です。ツールが返す値と実際にその関数を呼び出すものとの間のギャップこそが、本番環境での障害発生の根本原因となります。

専用設計の言語横断型、リポジトリ横断型の呼び出し元列挙は、関数削除のための開発者ワークフローの単なる改良ではありません。これは、エンタープライズコードベース内の非推奨関数が日常的に抱えているような、システム間呼び出し関係が蓄積されているほど複雑なシステムにおいて、そのワークフローを安全に実行するための前提条件です。完全な呼び出し元インベントリなしに削除された非推奨関数はすべて、欠落した定義に到達する特定の実行パスを待つ、未知の実行時エラーを含むリリースとなります。この未知のエラーを解消することが、構造化された呼び出し元検出の目的です。