19


2

なぜ私はDelphiで "with"を使わないのですか?

私は多くのプログラマー、特にDelphiプログラマーが 'with’の使用を否定するのを聞いたことがあります。

私はそれがプログラムをより速く走らせ(親オブジェクトへの1つの参照だけ)そして賢明に使用されるならコードを読むのがより簡単であると思った(1ダース未満のコード行とネスティングなし)。

これが一例です。

procedure TBitmap32.FillRectS(const ARect: TRect; Value: TColor32);
begin
  with ARect do FillRectS(Left, Top, Right, Bottom, Value);
end;

私は `with`を使うのが好きです。 私がどうかしましたか、まずいことでもありましたか?

16 Answer


33


withを使用することによる不快感の1つは、デバッガがそれを処理できないことです。 そのため、デバッグが難しくなります。

もっと大きな問題は、コードを読むのが簡単ではないということです。 特にwith文がもう少し長い場合。

TMyForm.ButtonClick(...)プロシージャはOtherFormで始まりますdo begin Left:= 10;トップ:= 20。 CallThisFunction;終わり;終わり;

どのフォームのCallThisFunctionが呼び出されますか? 自己(TMyForm)またはOtherForm? OtherFormにCallThisFunctionメソッドがあるかどうかを確認しないとわかりません。

そして最大の問題は、知らないうちにバグを簡単に解決できることです。 TMyFormとOtherFormの両方にCallThisFunctionがあるが、それが非公開の場合はどうなりますか。 OtherForm.CallThisFunctionが呼び出されることを期待または希望するかもしれませんが、実際はそうではありません。 withを使用しなかった場合、コンパイラは警告していたでしょうが、今はそうではありません。

withで複数のオブジェクトを使用すると、問題が倍増します。 http://blog.marcocantu.com/blog/with_harmful.htmlを参照してください。


11


ここでは、あいまいさを避けるためにwithブロック内のメンバーの前に `.`を付ける必要があるので、私はVB構文を好む。

With obj
    .Left = 10
    .Submit()
End With

しかし、実際には、 `with`には一般的に問題はありません。


11


次のようにして `with`文を拡張することができれば素晴らしいでしょう。

with x := ARect do
begin
  x.Left := 0;
  x.Rigth := 0;
  ...
end;

あなたは変数 'x’を宣言する必要はないでしょう。 コンパイラによって作成されます。 それは書くのが速くて混乱しません、どの機能が使われるか。


8


「with」を使用するとコードの実行速度が上がる可能性は低く、コンパイラが同じ実行可能コードにコンパイルする可能性が高いです。

人々が「with」を好まない主な理由は、名前空間の範囲と優先順位について混乱を招く可能性があるためです。

これが実際の問題である場合と、これが非問題である場合があります(非問題の場合は、「賢明に使用される」と質問で説明されているとおりです)。

混乱が生じる可能性があるため、混乱がない場合でも、「with」の使用を完全に控えることを選択する開発者もいます。 これは教義的に思えるかもしれませんが、コードが変更されて成長するにつれて、 "with"を混乱させる程度にコードを修正した後でも "wi​​th"の使用は残る可能性があると主張できます。そもそもその使い方を紹介します。


6


実際には:

procedure TBitmap32.FillRectS(const ARect: TRect; Value: TColor32);
begin
  with ARect do FillRectS(Left, Top, Right, Bottom, Value);
end;

and

procedure TBitmap32.FillRectS(const ARect: TRect; Value: TColor32);
begin
  FillRectS(ARect.Left, ARect.Top, ARect.Right, ARect.Bottom, Value);
end;

まったく同じアセンブラコードを生成します。

`with`節の値が関数またはメソッドの場合、パフォーマンスが低下する可能性があります。 この場合、もしあなたが良好なメンテナンスと良好なスピードを得たいのであれば、コンパイラーが舞台裏でやることをしてください。 一時変数を作成します。

実際には:

with MyRect do
begin
  Left := 0;
  Right := 0;
end;

これは、コンパイラによって擬似コードでエンコードされます。

var aRect: ^TRect;

aRect := @MyRect;
aRect^.Left := 0;
aRect^.Right := 0;

その場合、 aRect`は単なるCPUレジスタになることができますが、スタック上の真の一時変数になることもできます。 もちろん `TRect`は record`なのでここではポインタを使います。 オブジェクトは既にポインタなので、オブジェクトに対してはより直接的です。

個人的には、私は自分のコードで時々使っていましたが、asmが生成されるたびに、それが本来あるべきことを確実にするようにチェックしています。 誰もがそれを実行できる時間があるとは限らないので、私たち_local variable_はwithに代わる良い選択肢です。

私は本当にそのようなコードが好きではありません:

for i := 0 to ObjList.Count-1 do
  for j := 0 to ObjList[i].NestedList.Count-1 do
  begin
    ObjList[i].NestedList[j].Member := 'Toto';
    ObjList[i].NestedList[j].Count := 10;
  end;

それはまだでかなり読みやすいです:

for i := 0 to ObjList.Count-1 do
  for j := 0 to ObjList[i].NestedList.Count-1 do
  with ObjList[i].NestedList[j] do
  begin
    Member := 'Toto';
    Count := 10;
  end;

あるいは

for i := 0 to ObjList.Count-1 do
  with ObjList[i] do
  for j := 0 to NestedList.Count-1 do
  with NestedList[j] do
  begin
    Member := 'Toto';
    Count := 10;
  end;

しかし、内側のループが巨大なら、ローカル変数は意味があります。

for i := 0 to ObjList.Count-1 do
begin
  Obj := ObjList[i];
  for j := 0 to Obj.NestedList.Count-1 do
  begin
    Nested := Obj.NestedList[j];
    Nested.Member := 'Toto';
    Nested.Count := 10;
  end;
end;

このコードは `with`より遅くなることはありません:コンパイラは実際にそれを裏でします!

ちなみに、それはより簡単なデバッグを可能にするでしょう:あなたはブレークポイントを置いて、そして内部の値を得るために直接 Obj`か Nested`の上にあなたのマウスを向けることができます。


3


この議論はJavasciptでもたくさん起こります。

基本的に、With構文は、あなたがどのLeft / Top / etc property / methodを呼び出しているのか一目で見分けるのを非常に難しくします。あなたはLeftと呼ばれるローカル変数とプロパティを持つことができます。名前が間違っているなら申し訳ありません)Leftと呼ばれる、おそらくLeftと呼ばれる関数でさえも。 ARect構造にあまり精通していないコードを読んでいる人はだれでも非常に非常に失う可能性があります。


3


あなたがタイピングで節約するもの、あなたは読みやすさで失います。 多くのデバッガは、あなたが参照していることについての手がかりを持っていないでしょうから、デバッグはより困難です。 プログラムが速くなるわけではありません。

withステートメント内のコードを、参照しているオブジェクトのメソッドにすることを検討してください。


3


それは主にメンテナンスの問題です。

WITHの概念は言語の観点から合理的な意味を持ち、賢明に使用されたときにコードを小さくし、より明確にするという議論にはある程度の妥当性があります。 しかし問題は、ほとんどの商用コードはその生涯にわたって何人かの異なる人々によって維持され、書かれたときに小さくて簡単に解析される構造として始まるものは時間の経過と共に容易に変化してWITHの範囲が決まらない扱いにくい大きな構造になることがあるメンテナによって簡単に解析されます。 これは当然バグを生み出す傾向があり、それを見つけるのは困難です。

たとえば、WITHブロック内にラップされた3行または4行のコードを含む小さな関数fooがあるとしたら、実際には問題はありません。 しかし数年後、この関数は、何人かのプログラマーのもとでは、まだWITHで囲まれた40または50行のコードに拡張された可能性があります。 これは現在脆弱であり、特にメンテナが追加のWITHブロックを導入した場合に発生する可能性があるバグのために熟しています。

WITHには他の利点はありません - コードはまったく同じように解析され、同じ速度で実行されるべきです(私は3Dレンダリングに使用される狭いループの内側でD6でこれをいくつか実験しました) デバッガがそれを処理できないことも問題です - しかし、しばらく前に修正されているべきであり、何らかの利点があった場合には無視する価値があるでしょう。 残念ながらありません。


2


私はそれが好きではありません。 変数の値などをマウスでポイントしただけでは読み取れません。


2


あなたがそれを単純にしてあいまいさを避けている限り、それに関して何の問題もありません。

私が知っている限りでは、それは何もスピードアップしません - それは純粋に構文糖です。