LoginSignup
11
6

More than 1 year has passed since last update.

Delphi コードのクリーンアップ

Last updated at Posted at 2021-05-24

はじめに

業務アプリのコードを日々メンテしていると、度重なる仕様変更や機能の追加により、コンパイルするとヒントやワーニング (警告) が溜まり続ける事がありますよね?
image.png
正直にゆーてみ?再構築すると 3 桁とかあるじゃろ?

コードのクリーンアップ

ヒントとワーニングをゼロにすると単純にコンパイル速度が上がり、ビルドに無駄な時間を割かなくて済みます。また、表面化していないバグを炙り出すのにも有効です。

仕方のない事ですが、古いアプリケーションをマイグレーションしようとすると (Delphi をバージョンアップすると) エラーと共に大量のヒントやワーニングが出る事があります。しかしながら、バグを仕込んでいる可能性も否定できないため、放置せずにキッチリと潰しておいた方がいいかと思います。

...でも、中には「どうやったらこれ消せるの?」ってヒントやワーニングがあったりしますよね?

■ ヒント

$HINTS 指令によってヒント表示を一括でオンオフする事が出来ます。
※ 個別にはオンオフできないので、個別に対応したい場合には {$HINTS OFF} {$HINTS ON} で括る必要があります。

See also:

・H2077 '%s' に代入された値は使用されていません (Delphi)

原因:
メッセージの通り。

対処方法:
殆どのケースで、使われない代入は削除しても問題ない。

代入に何らかの計算式を伴っているのであれば、計算結果が使われていない事がバグである可能性もあるので、コメントアウトしてしばらく寝かせておくのもいいかもしれない。

このヒントを潰す退屈な作業をスクリプトを書いてサッサと終わらせたいと考えるかもしれない。コンパイルメッセージは IDE の [メッセージウィンドウ] のコンテキストメニューから保存できる。コンパイルログから H2077 を含む行を探し、行番号を得て、当該行をコメントアウトしてしまえばよい...

//  FValue := 0;

果たしてそうだろうか?

//FValue := FuncA(10) +
            FuncB(5) * 120;

上記のようなコードは多くの場合でエラーになるのでまだいいが...

  if a > 10 then
//  FValue := 0; 
  ProcA;

エラーもなくバグを生む事もある。

See also:

・H2164 変数 '%s' が宣言されていますが '%s' の中では使用されていません (Delphi)

原因:
メッセージの通り。

対処方法:
殆どのケースで、使わない変数は削除しても問題ない。

See also:

・H2219 %s:private 部で宣言されていますが,クラス内でまったく使用されていません (Delphi)

原因:
メッセージの通り。

対処方法1:
このヒントを出しているフィールドやメソッドを将来使う可能性があるのであれば {$HINTS OFF} {$HINTS ON} で括り、ヒントを握りつぶす。

  {$HINTS OFF}
  procedure HogeHoge;
  {$HINTS ON}

対処方法2:
もう使う事がないと判っているのであれば、素直にコードを削除した方がいい。

See also:

■ ワーニング

$WARN 指令によってワーニング表示を個別にオンオフする事が出来ます。
※ ワーニングをエラーに昇格させる事もできます。

See also:

・W1000 シンボル '%s' を使用することは推奨されていません (Delphi)

原因:
互換性のために残されている機能を使っている (deprecated ヒント指令でマークされている機能を使っている)。

対処方法 1:
多くは代替方法が提示されているハズ。

対処方法 2:
プロジェクトのオプションにある ヒントと警告使用を推奨されていないシンボルFalse に設定する (Delphi のバージョンによってはチェックを外す)。
image.png
非推奨には理由があるハズなのでこの方法を採る事はオススメしない

対処方法 3:
当該箇所を {$WARN SYMBOL_DEPRECATED OFF}{$WARN SYMBOL_DEPRECATED ON} で括ってもワーニングを消せるが、非推奨には理由があるハズなのでこの方法を採る事はオススメしない

FileAge()

例えば次のようなコードは FileAge() が非推奨の書式を使っているためワーニングが出る。

var
  FileName: String;
  dt: TDateTime;
begin
  Filename := 'TEST.TXT';
  dt := FileDateToDateTime(FileAge(FileName));
  ...

そこで、非推奨でない書式の方の FileAge() をラップした次のような関数を用意して、

function FileAgeToDateTime(const FileName: string): TDateTime;
begin
  if not FileAge(FileName, Result) then
    Result := 0;
end;

このように書き換える。

var
  FileName: String;
  dt: TDateTime;
begin
  Filename := 'TEST.TXT';
  dt := FileAgeToDateTime(FileName);
  ...
end;  

See also:

・W1002 シンボル '%s' はプラットフォームに依存すると宣言されています (Delphi)

原因:
特定のプラットフォームでしか使用できないものとしてマークされている機能を使用している。

対処方法 1:
プロジェクトのオプションにある ヒントと警告プラットフォーム依存のシンボルFalse に設定する (Delphi のバージョンによってはチェックを外す)。
image.png
対処方法 2:
このワーニングを出している機能を {$WARN SYMBOL_PLATFORM OFF} {$WARN SYMBOL_PLATFORM ON} で括る。

{$WARN SYMBOL_PLATFORM OFF}
  if DebugHook <> 0 then
    ReportMemoryLeaksOnShutdown := True;
{$WARN SYMBOL_PLATFORM ON}

See also:

・W1005 ユニット '%s' はプラットフォームに依存すると宣言されています (Delphi)

原因:
特定のプラットフォームでしか使用できないユニットを使用している。

対処方法 1:
プロジェクトのオプションにある ヒントと警告プラットフォーム依存のユニットFalse に設定する (Delphi のバージョンによってはチェックを外す)。
image.png
対処方法 2:
このワーニングを出しているユニットを uses 句で指定しているユニット (またはプロジェクトファイル) の先頭に {$WARN UNIT_PLATFORM OFF} を追加する。

unit Foo;
{$WARN UNIT_PLATFORM OFF}
...

See also:

・W1006 ユニット '%s' を使用することは推奨されていません (Delphi)

原因:
互換性のために残されているユニットを使っている (deprecated ヒント指令でマークされているユニットを使っている)。

対処方法 1:
多くは代替方法が提示されているハズ。ユニットのコメントヘッダ等も確認してみる。

対処方法 2:
プロジェクトのオプションにある ヒントと警告使用を推奨されていないユニットFalse に設定する (Delphi のバージョンによってはチェックを外す)。
image.png
非推奨には理由があるハズなのでこの方法を採る事はオススメしない

対処方法 3:
このワーニングを出しているユニットを uses 句で指定しているユニット (またはプロジェクトファイル) の先頭に {$WARN UNIT_DEPRECATED OFF} を追加してもワーニングを消せるが、非推奨には理由があるハズなのでこの方法を採る事はオススメしない

EOleException

例えば、古いコードだと EOleException を使うのに OleAutouses してあると思うが、現在では OleAuto は非推奨としてマークされている。

{*******************************************************}
{                                                       }
{            Delphi Visual Component Library            }
{                                                       }
{ Copyright(c) 1995-2021 Embarcadero Technologies, Inc. }
{              All rights reserved                      }
{                                                       }
{*******************************************************}

unit Vcl.OleAuto deprecated;

{$HPPEMIT LEGACYHPP}
{$DENYPACKAGEUNIT}

{ OleAuto cannot be used in a package DLL.  To implement
  an OLE automation server in a package, use the new
  OLE automation support in comobj and comserv.
}

コメントにあるように、現在では EOleExceptionComObj で定義されているため、uses 句にある OleAutoComObj で置き換えるとワーニングを解消できる。

See also:

・W1009 '%s' の再宣言のため基本クラスのメンバーが隠蔽されました (Delphi)

原因:
コンポーネントやフォームを派生した際、知らずに基底クラスのメンバーと同じ名前のプロパティを作ってしまったパターンが多いが、製品のバージョンアップに伴い、基底クラスに新しいメンバーが追加された事で発生する事もある。

「意図しないプロパティ名競合の可能性」

と読み替えるとスッキリするかと思う。

対処方法1:
リネームで解決するのならリネーム。

対処方法2:
RTTI を使っているなどの理由 (プロパティ名が外的要因でネーミングされている) により、プロパティ名をリネームできないのであれば、当該箇所を {$WARN HIDING_MEMBER OFF}{$WARN HIDING_MEMBER ON} で括る。

    {$WARN HIDING_MEMBER OFF}
    property UNITNAME: String read FUNITNAME write FUNITNAME;
    {$WARN HIDING_MEMBER ON}

See also:

・W1010 メソッド '%s' で基底型 '%s' の仮想メソッドが隠蔽されます(Delphi)

原因:
コンポーネントやフォームを派生した際、知らずに基底クラスの仮想メソッドと同じ名前のメソッドを作ってしまったパターンが多いが、製品のバージョンアップに伴い、基底クラスに新しい仮想メソッドが追加された事で発生する事もある。

「意図しないメソッド名競合の可能性」

と読み替えるとスッキリするかと思う。

対処方法:
コンポーネントやフォームが目的の通りに動作しているのであれば、ワーニングの出ているメソッドに reintroduce キーワードを付けるのが手っ取り早い。

procedure VirtuMethod; reintroduce;

基底クラスに同名の仮想メソッドがある事を知らずに同じ名前を付けたのであれば、気付いた時点でリネームして名前の競合を解消する。既にあちこちで使われてしまってリファクタリングやドキュメントの修正が大変になっているのなら、妥協して reintroduce キーワードを付けるしかない。

マイグレーション時に発生した (旧バージョンをビルドしても出ない) 際は、動作には影響がないので、多くの場合で reintroduce キーワードを付ければいいと思うが、旧バージョンでリネームして動作確認したものをマイグレーションするのが筋だとは思う。

See also:

・W1011 コンパイラは 'end.' 以降の文字を無視します - (Delphi)

原因:
コンパイルエラーが出ていないのであれば、ユニット末尾の end. 以降にも何かが書かれている。

対処方法:
end. 以降をフリーに書けるメモ帳代わりに使いたいのなら、ユニットのどこかに {$WARN GARBAGE OFF} を記述しておくといい。
image.png
プロジェクトのオプション (.END 以降へのテキストの記述。コンパイラはこれを無視する。) でもワーニングを消せるが、ユニットを他のプロジェクトで使いまわそうとすると、やっぱりそこでワーニングになってしまう。それと、あちこちのユニットの末尾をメモ帳代わりにするくらいなら、普通にテキストファイルを一つ用意してプロジェクトに追加した方がいいように思う。

See also:

・W1020: クラス '%s' のインスタンスを作成していますが,このクラスは抽象メソッド '%s.%s' を含みます (Delphi)

原因:
抽象メソッドを持ったクラスのインスタンスを作成しようとしたか、基底クラスに抽象メソッドが追加された。

対処方法:
ドキュメントにあるように抽象メソッドを持ったクラスのインスタンスを作成しようとしたのかもしれないが、使っていたクラスの基底クラスに抽象メソッドが追加された時もこのワーニングが発生する。この場合、ワーニングの出たクラスに基底クラスで追加された抽象メソッドを実装しなくてはならない。

See also:

・W1030: コンパイラ指令に間違いがあります:'%s' (Delphi)

原因:
W1030 間違ったコンパイル指令 : 'true' というワーニングの場合、多くは古いプロジェクトを XE5 以降に持ってきた際に発生している。プロジェクトオプションの値が XE5 で変更になっている事が原因。

対処方法:
[プロジェクトオプション] でコンパイルオプションを開き、[ターゲット] で すべての構成 を選んでから、デバッグ情報の値を "デバッグ情報" に変更。変更後、念のために他のターゲットも確認。
image.png
この際、プロジェクトファイル (*.dpr) の中身が壊れる事があるので、事前に外部のテキストエディタ等へ内容をコピーしておく。必要なのはプロジェクトファイルの上部に生成されるコンパイル指令だけなので、その下はすべて退避させた内容を書き戻しても大丈夫。

W1030 間違ったコンパイル指令 : 'xxxxx' の xxxxx 部分でコンパイル指令が特定できない場合には、プロジェクトファイル (*.dpr, *.dproj) を削除して作り直した方がいい (早い) かもしれない。

See also:

・W1036 変数 '%s' は初期化されていない可能性があります (Delphi)

原因:
変数に値が割り当てられていないまま、その値を使おうとした場合に発生する。

対処方法:
多くの場合、

procedure Proc;
var
  a, b: Integer;
begin
  a := Random(10);
  if a < 5 then
    b := 10;
  Writeln(b); // ここでワーニング
end;

ブロックの先頭で変数を初期化するだけでいい。

procedure Proc;
var
  a, b: Integer;
begin
  a := Random(10);
  b := 0; // 変数 b を初期化
  if a < 5 then
    b := 10;
  Writeln(b);
end;

Delphi 10.3 以降のインライン変数宣言を使えば割とスッキリ解決できる。

procedure Proc;
begin
  var a := Random(10);
  var b := 0; // 変数 b を初期化
  if a < 5 then
    b := 10;
  Writeln(b);
end;

See also:

・W1050 set 式で WideChar がバイト文字に変換されました (Delphi)

原因:
Unicode 文字の集合に対して in 演算子を使った場合に発生する。多くの場合、ANSI 版 Delphi から Unicode 版 Delphi へのマイグレーション過程で発生。ANSI 版 Delphi 用しかないコンポーネントやライブラリを Unicode 版 Delphi で使おうとした時にも発生する。

対処方法:
殆どのケースでは CharInSet() で置き換えればいいだけだが、

  for i:=1 to Length(S) do
//  if (S[1] in ['A'..'Z']) then
    if CharInSet(S[1], ['A'..'Z']) then // CharInSet() で置き換え
      ShowMessage('Hit!');

置換するとおかしくなりそうなロジックが含まれる場合には、ワーニングをエラーに昇格してエラーを徹底的に潰した方がいい。中途半端に型キャストで逃げると、後で問題が発生した場合に修正箇所を探すのが困難になる。

W1050 が発生するユニットの先頭に次のコードを挿入すると、ワーニングがコンパイルエラーで停止するようになる。

// "set 式で WideChar がバイト文字に変換されました(W1050)"をエラーに昇格
{$WARN WIDECHAR_REDUCED ERROR}

エラーをすべて潰さないと実行形式ファイルが作られないため、懸念される箇所を放置できなくなる。エラーをすべて解消してから当該コードを外すようにする。

See also:

・W1055 Published によって型に RTTI ($M+) が追加されます '%s'

原因:
多くはコンポーネントでない (TPersistent の下位クラスでない) クラスのプロパティが published で公開されている事により発生する。

既存のコンポーネントクラスのロジックをパクってインスパイアしたクラスを作ったか、RTTI (実行時型情報) を使うにもかかわらず、コンパイラ指令を付け忘れているかのいずれかだと推察される。

対処方法1:
published にあるプロパティを public に移動する。基本はコレでいいと思う。

type
  THoge = class(TObject)
    ...
  public
    ...
  published 
    property  FileName: string read FFileName write SetFileName;
  end;

type
  THoge = class(TObject)
    ...
  public
    ...
    property  FileName: string read FFileName write SetFileName;
  end;

対処方法2:
クラスを TPersistent から派生させる。

type
  THoge = class(TPersistent)

対処方法3:
このワーニングを出しているクラスの宣言部を {$TYPEINFO ON} {$TYPEINFO OFF} (または {$M+} {$M-}) で括る。

{$TYPEINFO ON}
  THoge = class(TObject)
    ...
{$TYPEINFO OFF}

See also:

・W1057 文字列の暗黙的なキャスト ('%s' から '%s') (Delphi)
・W1058 データ損失の可能性がある文字列の暗黙的なキャスト ('%s' から '%s') (Delphi)

原因:
異なる文字列型への代入で起こる。多くの場合、ANSI 版 Delphi から Unicode 版 Delphi へのマイグレーション過程で発生。ANSI 版 Delphi 用しかないコンポーネントやライブラリを Unicode 版 Delphi で使おうとした時にも発生する。

対処方法:
殆どのケースでは明示的に型キャストすればいいだけだが、型キャストするとおかしくなりそうなロジックが含まれる場合には、ワーニングをエラーに昇格してエラーを徹底的に潰した方がいい。中途半端に型キャストで逃げると、後で問題が発生した場合に修正箇所を探すのが困難になる。

W1057W1058 が発生するユニットの先頭に次のコードを挿入すると、ワーニングがコンパイルエラーで停止するようになる。

// "文字列の暗黙的なキャスト(W1057)"をエラーに昇格
{$WARN IMPLICIT_STRING_CAST ERROR}

// "データ損失の可能性がある文字列の暗黙的なキャスト(W1058)"をエラーに昇格
{$WARN IMPLICIT_STRING_CAST_LOSS ERROR}

エラーをすべて潰さないと実行形式ファイルが作られないため、懸念される箇所を放置できなくなる。エラーをすべて解消してから当該コードを外すようにする。

キャストで "逃げた" 場合:
AnsiString を UnicodeString にキャストしても大きな問題はまず起きないが、UnicodeString を AnsiString にキャストすると文字の損失が起こる可能性が高い。

明示的にキャストしているコードのためのワーニングである W1059 / W1060 も用意されているが、デフォルトで無効になっている。キャストで逃げた箇所を再調査したい場合には、プロジェクトオプションでこの 2 つのワーニングを有効にする事ができる。

See also:

おわりに

とりあえず思いついたヒントとワーニングへの対処方法を並べてみました。
他にも何かあったら備忘録的に順次追記したいと思います。

そういえば

Delphi の古い書籍や記事では、

  • 「IDE のコンパイラ指令に依存しないように、コードにコンパイラ指令を書いておけ!」
  • 「〔Ctrl〕+〔O〕,〔O〕でユニットの先頭に現在のコンパイラ指令が出力されるぞ!」

って、書いてあったりしますが、この記事を書いた時点の最新版 Delphi である 10.4 Sydney でそれをやると...

{$A8,B-,C+,D+,E-,F-,G+,H+,I+,J-,K-,L+,M-,N-,O+,P+,Q-,R-,S-,T-,U-,V+,W-,X+,Y+,Z1}
{$MINSTACKSIZE $00004000}
{$MAXSTACKSIZE $00100000}
{$IMAGEBASE $00400000}
{$APPTYPE GUI}
{$WARN SYMBOL_DEPRECATED ON}
{$WARN SYMBOL_LIBRARY ON}
{$WARN SYMBOL_PLATFORM ON}
{$WARN SYMBOL_EXPERIMENTAL ON}
{$WARN UNIT_LIBRARY ON}
{$WARN UNIT_PLATFORM ON}
{$WARN UNIT_DEPRECATED ON}
{$WARN UNIT_EXPERIMENTAL ON}
{$WARN HRESULT_COMPAT ON}
{$WARN HIDING_MEMBER ON}
{$WARN HIDDEN_VIRTUAL ON}
{$WARN GARBAGE ON}
{$WARN BOUNDS_ERROR ON}
{$WARN ZERO_NIL_COMPAT ON}
{$WARN STRING_CONST_TRUNCED ON}
{$WARN FOR_LOOP_VAR_VARPAR ON}
{$WARN TYPED_CONST_VARPAR ON}
{$WARN ASG_TO_TYPED_CONST ON}
{$WARN CASE_LABEL_RANGE ON}
{$WARN FOR_VARIABLE ON}
{$WARN CONSTRUCTING_ABSTRACT ON}
{$WARN COMPARISON_FALSE ON}
{$WARN COMPARISON_TRUE ON}
{$WARN COMPARING_SIGNED_UNSIGNED ON}
{$WARN COMBINING_SIGNED_UNSIGNED ON}
{$WARN UNSUPPORTED_CONSTRUCT ON}
{$WARN FILE_OPEN ON}
{$WARN FILE_OPEN_UNITSRC ON}
{$WARN BAD_GLOBAL_SYMBOL ON}
{$WARN DUPLICATE_CTOR_DTOR ON}
{$WARN INVALID_DIRECTIVE ON}
{$WARN PACKAGE_NO_LINK ON}
{$WARN PACKAGED_THREADVAR ON}
{$WARN IMPLICIT_IMPORT ON}
{$WARN HPPEMIT_IGNORED ON}
{$WARN NO_RETVAL ON}
{$WARN USE_BEFORE_DEF ON}
{$WARN FOR_LOOP_VAR_UNDEF ON}
{$WARN UNIT_NAME_MISMATCH ON}
{$WARN NO_CFG_FILE_FOUND ON}
{$WARN IMPLICIT_VARIANTS ON}
{$WARN UNICODE_TO_LOCALE ON}
{$WARN LOCALE_TO_UNICODE ON}
{$WARN IMAGEBASE_MULTIPLE ON}
{$WARN SUSPICIOUS_TYPECAST ON}
{$WARN PRIVATE_PROPACCESSOR ON}
{$WARN UNSAFE_TYPE OFF}
{$WARN UNSAFE_CODE OFF}
{$WARN UNSAFE_CAST OFF}
{$WARN OPTION_TRUNCATED ON}
{$WARN WIDECHAR_REDUCED ON}
{$WARN DUPLICATES_IGNORED ON}
{$WARN UNIT_INIT_SEQ ON}
{$WARN LOCAL_PINVOKE ON}
{$WARN MESSAGE_DIRECTIVE ON}
{$WARN TYPEINFO_IMPLICITLY_ADDED ON}
{$WARN RLINK_WARNING ON}
{$WARN IMPLICIT_STRING_CAST ON}
{$WARN IMPLICIT_STRING_CAST_LOSS ON}
{$WARN EXPLICIT_STRING_CAST OFF}
{$WARN EXPLICIT_STRING_CAST_LOSS OFF}
{$WARN CVT_WCHAR_TO_ACHAR ON}
{$WARN CVT_NARROWING_STRING_LOST ON}
{$WARN CVT_ACHAR_TO_WCHAR ON}
{$WARN CVT_WIDENING_STRING_LOST ON}
{$WARN NON_PORTABLE_TYPECAST ON}
{$WARN XML_WHITESPACE_NOT_ALLOWED ON}
{$WARN XML_UNKNOWN_ENTITY ON}
{$WARN XML_INVALID_NAME_START ON}
{$WARN XML_INVALID_NAME ON}
{$WARN XML_EXPECTED_CHARACTER ON}
{$WARN XML_CREF_NO_RESOLVE ON}
{$WARN XML_NO_PARM ON}
{$WARN XML_NO_MATCHING_PARM ON}
{$WARN IMMUTABLE_STRINGS OFF}

80 行のコンパイラ指令がユニットの先頭に追加されます。そのままだとかなりウザいです。{$REGION} {$ENDREGION} で括れない (畳めない) のが地味に痛い。インクルードファイル (*.inc) で処理すべきですかねぇ...。

CompilerDirectives.inc
{$A8,B-,C+,D+,E-,F-,G+,H+,I+,J-,K-,L+,M-,N-,O+,P+,Q-,R-,S-,T-,U-,V+,W-,X+,Y+,Z1}
{$MINSTACKSIZE $00004000}
{$MAXSTACKSIZE $00100000}
...
{$WARN IMMUTABLE_STRINGS OFF}
Unit1.pas
{$I 'CompilerDirectives.inc'}
unit Unit1;

interface
...
11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6