エムオーテックス株式会社が運営するテックブログです。

製品品質向上への取り組み 第 1 弾:Delphi の静的コード解析によるコードレビュー支援

製品品質向上への取り組み 第 1 弾:Delphi の静的コード解析によるコードレビュー支援

はじめに

こんにちは、システム開発部の奥野です。
エムオーテックスでは、製品開発の品質と生産性を継続的に高める取り組みを進めています。
今回は、その一環として Delphi の静的コード解析を導入し、保守性と可読性の向上を目指した試みをご紹介します。

背景と課題

LANSCOPE エンドポイントマネージャー オンプレミス版は主に Delphi(Object Pascal) で開発しています。

www.embarcadero.com

Delphi は Windows 向けデスクトップアプリケーションを迅速に開発できます。一方で、オブジェクトやリソースの生成・解放(メモリ管理) をコードで適切に扱う必要があります。
これが不十分だと メモリリーク(使い終えたメモリが解放されず蓄積する現象) が発生し、長期運用で徐々に悪影響を及ぼすことがあります。

エムオーテックスでは、メモリ管理が適切か、自動解放の仕組みが妥当かなどをレビューで重点的に確認してきました。 しかし、レビューアーの経験や注意力に依存しがちで、検出のばらつきや負担が課題でした。
加えて、動作確認やテストで発見できても開発工程の後半になりがちで、対応工数が増加していました。

仮説と狙い

コーディング時点で懸念箇所を見つけられれば、早期に対処できるはずです。
そこで、ソースコードを実行せずにチェックする 静的コード解析(コード上のパターンや規約違反を機械的に検出する手法) に着目しました。
静的コード解析は、実行時のメモリ消費やリークを直接測定するものではありませんが、生成と解放の不整合例外時に解放されない可能性所有権が曖昧 など、コードだけでも検出できる懸念を拾えるためレビューの補完になると考えました。

調査の進め方

市場にある Delphi 向けの静的コード解析ツールを個別に調査し、どのような解析が可能か、とくにメモリ関連の課題を検出できるかに着目しました。
問題のあるサンプルコードを 6 パターン用意し、解析結果を比較しました。

  1. 途中で例外が発生すると解放漏れになるパターン
  2. 生成と解放が対になっていないパターン
  3. 条件によっては解放処理を通らないパターン
  4. try...finally の範囲指定の誤り
  5. 複数回生成されたオブジェクトが解放されないパターン
  6. 未生成のオブジェクト操作や、解放後のオブジェクト操作

検証に使用した 6 パターンのサンプルコード

// 1. 途中で例外が発生すると解放漏れになるパターン
function MyFunc_01(): Boolean;
const
  moji = 'aiueo';
var
  ii: Integer;
  tmpList: TList;
  tmpClass: TMyClass;
  n: Integer;
begin
  //
  tmpList := TList.Create;
  try
    for ii := 0 to 3 do
    begin
      tmpClass := TMyClass.Create;
      n := StrToInt(moji);  // ここでエラーになると tmpClass が解放されない
      tmpClass.SeqNo := n;
      tmpList.Add(tmpClass);
    end;
  finally
    for ii := 0 to tmpList.count -1 do
    begin
      TMyClass(tmpList[ii]).Free;
    end;
    tmpList.Free;
  end;

  result := true;
end;
// 2. 生成と解放が対になっていないパターン
function MyFunc_02(): Boolean;
var
  tmpClass: TMyClass;
begin
  tmpClass := TMyClass.Create;
  try
    tmpClass.SeqNo := 100;
  finally
    //FreeAndNil(tmpClass);   // 解放していない
  end;

  result := true;
end;
// 3. 条件によっては解放処理を通らないパターン
function MyFunc_03(): Boolean;
var
  ii: Integer;
  tmpList: TList;
  tmpClass: TMyClass;
begin
  //
  tmpList := TList.Create;
  try
    for ii := 0 to 3 do
    begin
      tmpClass := TMyClass.Create;
      if ii = 2 then
      begin
        // 特定条件時にしか Add しないので解放漏れになる
        tmpClass.SeqNo := ii;
        tmpList.Add(tmpClass);
      end;
    end;
  finally
    for ii := 0 to tmpList.count - 1 do
    begin
      TMyClass(tmpList[ii]).Free;
    end;
    tmpList.Free;
  end;

  result := true;
end;
// 4. try...finally の範囲指定の誤り
function MyFunc_04(): Boolean;
var
  ii: Integer;
  tmpClass: TMyClass;
begin
  try
    // Create に失敗しても解放しようとしてエラーになる可能性
    tmpClass := TMyClass.Create;
    tmpClass.SeqNo := 100;
  finally
    tmpClass.Free;
  end;

  result := true;
end;
// 5. 複数回生成されたオブジェクトが解放されないパターン
function MyFunc_05(): Boolean;
var
  tmpClass: TMyClass;
begin
  tmpClass := TMyClass.Create;
  try
    tmpClass := TMyClass.Create;
    try
      tmpClass.SeqNo := 100;
    finally
      FreeAndNil(tmpClass);
    end;
  finally
    FreeAndNil(tmpClass);
  end;

  result := true;
end;
// 6. 未生成のオブジェクト操作や、解放後のオブジェクト操作
function MyFunc_06(): Boolean;
var
  tmpClass: TMyClass;
begin
  try
    tmpClass.FSeqNo := 0;  // Create 前なのに操作

    tmpClass := TMyClass.Create;
    try
      tmpClass.SeqNo := 100;
    finally
      FreeAndNil(tmpClass);
    end;

    tmpClass.FSeqNo := 12;   // FreeAndNil 後なのに操作
  except
  end;

  result := true;
end;

試したツールの中で Pascal Analyzer は懸念箇所を的確に指摘することを確認しました。

www.peganza.com

レポートでは「どのファイルのどの行に、どのような懸念があるか」が明示され、開発者やレビューアーが該当箇所を素早く確認できます。
また、メモリ管理以外にも危険性の高いコード、保守性や可読性の低下を招く記述など多数の観点があることも確認できました。

補足として、同じサンプルコードを生成 AI(OpenAI o3-mini)に解析させたところ、初回は一部のパターンで検出漏れがありました。
「本当にメモリ関連の懸念はないか」と追加で指示すると全パターンを検出できましたが、当時の検証条件では網羅性に課題が残る可能性を感じています。
モデルの能力向上やプロンプト設計の工夫により改善が期待できる点です。

運用と結果

静的コード解析のレポートをコードレビューのインプットとして、一部のチームで試験運用しました。
レビューの流れは、チーム内レビューを経てから私がレビューを行う形です。
プルリクエスト作成時点で解析を実行し、私のレビューコメントとあわせて解析結果もフィードバックしました。

コードレビュー時に静的コード解析を含んだフロー図

試験運用で得られた気づきは以下のとおりです。

  1. メモリ管理に限らず、ツールは毎回同じルールで検出するため、人による見落としや判断のばらつきが減る
  2. 特定条件で解放漏れになるパターンなども的確に指摘され、注力すべきポイントに集中できた
  3. 具体的にどの条件で解放漏れにつながるかは、解析結果とコード内容をもとに確認が必要
  4. 別のタイミングで解放しているなど、仕様上問題ない指摘もあり、精査が不可欠

チームの意見も概ね同じで、懸念を早期に解消できる期待が持てました。
暗黙的な型変換などの細かな指摘も保守性・可読性の向上に役立つと実感しています。
もちろん、静的コード解析は万能ではありません。動的な挙動に起因する問題はテストや運用時の監視も重要で、解析はそれらを補完する位置づけです。

また、3・4 の点に関しては、新規参入者や経験の浅いメンバーには解析結果の理解が難しい場合もありました。
解析自体は数秒から数分で完了しますが、結果の精査に時間がかかるという課題も見えてきました。

副次的な学び

解析ツールの結果や観点を調べる過程で、Delphi の仕様理解が深まりました。
例えば「ローカル変数は宣言時に初期化が保証されない一方、文字列変数などは自動的に既定値(空文字など)に初期化される」など、細かな仕様を再確認できました。
また、所有権の明確化などのメモリ管理に関するベストプラクティスを整理できました。
静的解析ツールの指摘内容から学べる機会も多く、これらの知見をレビュー基準の共通化にも活用していきます。

新たな課題と次の一手

試験運用したチームでは、56 件の指摘の精査に約 1 週間を要しました。
検出自体は自動でも、それが不具合か仕様上問題ないかの判断にはスキルと時間が必要です。
この精査工程の効率化に向け、生成 AI を活用して解析結果の優先度付けや根拠提示を支援する構想を進めています。

おわりに

静的コード解析は、コーディングやコードレビューの段階で懸念を洗い出す有効な手段です。
一貫した基準による自動指摘により、レビューアーは重要な部分に集中でき、レビュー品質の向上が期待できます。
とくにメモリ管理は、Delphi 開発における重要な観点であり、静的コード解析で課題を検出できたことは大きな収穫でした。

次回の記事では、解析結果の精査効率を高めるための生成 AI 活用についてご紹介する予定です。