为什么我不应该在Delphi中使用“with”?
我听说过很多程序员,尤其是Delphi程序员不屑于使用'with'。
我认为它使程序运行得更快(只有一个对父对象的引用),并且如果明智地使用(less于十几行代码并且不嵌套),则更容易阅读代码。
这是一个例子:
procedure TBitmap32.FillRectS(const ARect: TRect; Value: TColor32); begin with ARect do FillRectS(Left, Top, Right, Bottom, Value); end;
我喜欢用。 我怎么了?
一个恼人的使用是debugging器不能处理它。 所以它使debugging更加困难。
更大的问题是阅读代码不太容易。 特别是如果with语句有点长。
procedure TMyForm.ButtonClick(...) begin with OtherForm do begin Left := 10; Top := 20; CallThisFunction; end; end;
哪个窗体的CallThisFunction将被调用? 自我(TMyForm)或其他表单? 如果不检查OtherForm是否具有CallThisFunction方法,则无法知道。
而最大的问题是,即使不知道它,也可以使错误变得容易。 如果TMyForm和OtherForm都有一个CallThisFunction,但它是私有的。 你可能期望/想要调用OtherForm.CallThisFunction,但实际上并不是这样。 如果你不使用with,编译器会提醒你,但现在不会。
使用与多个对象乘以问题。 请参阅http://blog.marcocantu.com/blog/with_harmful.html
在这种情况下,我更喜欢VB语法,因为在这里,你需要在with块的前面加上a .
避免含糊不清:
With obj .Left = 10 .Submit() End With
但是真的,一般来说没有什么问题。
如果with
下述方式扩大with
声明,那将是非常好的:
with x := ARect do begin x.Left := 0; x.Rigth := 0; ... end;
你不需要声明一个variables“x”。 它将由编译器创build。 编写起来很快,而且没有混淆,使用哪个函数。
“with”不太可能使代码运行得更快,编译器更可能将其编译为相同的可执行代码。
人们不喜欢“与”的主要原因是它会引起关于命名空间范围和优先级的混淆。
有些情况下,这是一个真正的问题,以及这是一个非问题的情况(非问题的情况将在问题中描述为“明智地使用”)。
由于可能的混淆,一些开发者select不使用“完全”,即使在没有这种混淆的情况下也是如此。 这可能看起来是教条式的,然而可以认为,随着代码的变化和增长,即使在代码被修改到可能使“with”混淆的程度之后,“with”的使用也可能保持不变,因此最好不要首先介绍它的用途。
事实上:
procedure TBitmap32.FillRectS(const ARect: TRect; Value: TColor32); begin with ARect do FillRectS(Left, Top, Right, Bottom, Value); end;
和
procedure TBitmap32.FillRectS(const ARect: TRect; Value: TColor32); begin FillRectS(ARect.Left, ARect.Top, ARect.Right, ARect.Bottom, Value); end;
将生成完全相同的汇编代码。
如果with
子句的值是一个函数或一个方法,性能可能会下降。 在这种情况下,如果要保持良好的维护和良好的速度,只需执行编译器在场景后面执行的操作,即创build一个临时variables 。
事实上:
with MyRect do begin Left := 0; Right := 0; end;
由编译器以伪代码编码:
var aRect: ^TRect; aRect := @MyRect; aRect^.Left := 0; aRect^.Right := 0;
然后aRect
可以只是一个CPU寄存器,但也可以是一个真正的临时variables在堆栈上。 当然,因为TRect
是一个record
TRect
我在这里使用指针。 对象更直接,因为它们已经是指针了。
就我个人而言,我有时在我的代码中使用,但我几乎每次检查生成的asm,以确保它应该做什么。 不是每个人都能够或有时间做到这一点,所以恕我直言, 本地variables是一个很好的select。
我真的不喜欢这样的代码:
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;
但是如果内部循环是巨大的,局部variables确实是有意义的:
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;
这段代码不会慢于:编译器事实上是在幕后!
顺便说一句,它将允许更简单的debugging:您可以放置一个断点,然后将鼠标指向Obj
或直接Nested
来获取内部值。
这个争论也发生在Javascipt。
基本上,使用语法使得很难一目了然地告诉你正在调用哪个Left / Top / etc属性/方法。你可以有一个名为Left的局部variables和一个属性(我已经有一段时间了做delphi,不好意思,如果名称是错误的)称为左,甚至可能是一个名为左function。 任何读取不熟悉ARect结构的代码都可能会非常丢失。
在打字时你节省了你的可读性。 许多debugging器都不知道你所指的是什么,所以debugging比较困难。 它不会使程序运行得更快。
考虑在你的with语句中使用你引用的对象的方法。
这主要是一个维护问题。
从语言的angular度来看,WITH的思想是合理的,当它合理使用时,代码保持小而清晰的说法是有效的。 然而,问题在于,大多数商业代码将由一些不同的人维护,而且在开始时,作为一个小的,易于parsing的构造,在编写时可以很容易地随着时间的推移变成难以处理的大型结构,其中WITH的范围不是很容易被维护人员parsing。 这自然会产生错误,而且很难find。
例如,假设我们有一个小函数foo,其中包含三行或四行代码,这些代码已经被封装在一个WITH块中,那么确实没有问题。 然而几年之后,这个函数可能已经在几个程序员的基础上扩展成40或50行代码,这些代码仍然包含在一个WITH中。 这个现在变得很脆弱,并且已经成熟了,尤其是如果维护人员介绍了更多的embedded的WITH块的话。
WITH没有其他的好处 – 代码应该被parsing完全一样,并以相同的速度运行(我在D6内部用于3D渲染的紧密循环中做了一些实验,我可以find没有区别)。 debugging器无法处理它也是一个问题 – 但是应该早一点修复,如果有任何好处,值得忽略。 不幸的是没有。
我不喜欢它,因为它使讨厌的麻烦。 只要用鼠标hover在上面,就无法读取variables的值。
在工作中,我们给出了从现有Win 32代码库中删除Withs的要点,因为需要额外的努力来维护使用它们的代码。 我在之前的一个工作中发现了一些错误,其中一个名为BusinessComponent的局部variables被一个与一个已发布属性BusinessComponent相同types的对象的开始块所掩盖。 编译器select使用发布的属性和代码来使用本地variables崩溃。
我看过类似的代码
与a,b,c,d做{除了他们是更长的名字,在这里缩短)开始i:= xyz;
结束;
试图findxyz的来源可能是一个真正的痛苦。 如果是c,我会更早写下来
我:= c.xyz;
你认为这是相当简单的了解这一点,但不是在800线长的function,在一开始就使用了正确的!
你可以结合语句,所以你最终
with Object1, Object2, Object3 do begin //... Confusing statements here end
如果你认为debugging器被一个人搞糊涂了,我不知道有人怎么知道在这个块上发生了什么
只要保持简单并避免含糊不清,就没有任何问题。
据我所知,它并没有加快速度 – 这纯粹是句法糖。
它允许无能或邪恶的程序员编写不可读的代码。 因此,只有使用这个function,如果你既无能也不邪恶。
… 跑得更快 …
不一定 – 您的编译器/解释器通常比优化代码更好。
我想这让我说“哎呀!” 因为它是懒惰的 – 当我正在阅读代码(特别是其他人)时,我喜欢看到明确的代码。 所以我甚至会在Java中写“this.field”而不是“field”。
我们最近在Delphi编码标准中禁止了它。
专业人士经常超过这个缺点。
这是由于滥用而引入的错误。 这些都没有理由能够节省时间来编写或执行代码。
是的,使用可以导致(温和)更快的代码执行。
在下面,foo只被评估一次:
with foo do begin bar := 1; bin := x; box := 'abc'; end
但是,这里评估三次:
foo.bar := 1; foo.bin := x; foo.box := 'abc';
对于Delphi 2005而言,在待办事宜陈述中存在硬性错误 – 评估指针丢失,并且用指针向上移动。 必须使用局部variables,而不是直接使用对象types。
- 为什么一个Delphi程序员使用Lazarus作为IDE而不是使用Delphi的IDE?
- 我如何searchDelphi文档?
- 如何让比超越比较忽略某些差异,同时比较版本的Delphi窗体文件
- 如何从Delphi程序或编译器生成的debugging信息中提取本地variables信息(地址和types)?
- 在Delphi中使用/不使用JCL托pipeCLR – 例子
- 为什么WideString不能用作interop的函数返回值?
- 在更改某些非文本字符的字体时,如何使TRichEdit在Windows 7上的行为类似于写字板?
- 如何使用MSBuild和Delphi XE2构build
- 如何让Delphi知道我已经处理了一个exception?