Tuesday, August 10, 2004

The with-statement considered harmful

As have been mentioned by several people in several places (newsgroups, blogs, QC etc.) Delphi's with-statements can be harmful to the readability, maintainability and robustness of your code. Not to mention it makes debugging harder ;).

Much like the problem with potential duplicate identifiers from used unit scopes, the with-statement problem could be mitigated by enhancing the compiler to produce Warnings for ambiguous with-statements.

Consider the following code:

Unit Foo; 
type
TGadget = class
bar: integer;
end;
//----
Unit Bar;
var
a: TGadget;
foo: integer
begin
with a do
foo := 123;
end;

This is version 1. Code compiles without warnings and works correctly. Now Mr. Component on the other side of the globe makes an update to the TGadget class:

Unit Foo; 
type
TGadget = class
foo: integer; // New feature!
bar: integer;
end;

Then John Doe recompiles his code with this new version:

Unit Bar; 
var
a: TGadget;
foo: integer
begin
with a do
foo := 123;
end;

Currently, with D7, there is no warning. Code compiles, but it fails mysteriously at run-time. In a future version of the compiler he could get warning such as: Warning: Potential name conflict: foo declared in unit Bar and Foo.TGadget. In this case, to get rid of the warning, the programmer could change the code to

   Bar.foo := 123; 

or (even better) he could just get rid of the with-statement.


IMO, if we could have this kind of warning for with-statements, it would be the first steps towards patching one of the very few sorespots of the otherwise elegant Object Pascal language. (and before you ask, yes, this has been QC'ed: #8770).

8 comments:

Anonymous said...

Oh, man! Now I have to take some of my votes off your uses-clause QC entry!

Anonymous said...

I recommend the with statement be extended to allow it to also be written as such:
with a: Form1 do
begin
a.foo := 123;
a.bar := 'xyz';
a.Caption := 'Test';
Caption := 'Foo Bar'; //The forms caption
end;

Obviously one could have declared a variable named 'a' of same type as Form1, but I think the above syntax is cleaner and 'a' is only defined in the scope of the with statement, also it's quite similar to the way exceptions are referenced in a try...except..end; statement, so seems like a logical extension.

the above will also allow for the following common syntax:

with frm: TMyForm.Create(nil) do
try
frm.ShowModal;
finally
frm.Free;
end;

N. Ismail

Hallvard Vassbotn said...

Interesting comment, see my reply in the next blog post

Paulo Fran├ža said...

There are situations where WITH statements are very useful, such as when instantiating 'in-stack" objects:

    with TAnyClass.Create do
    try
       // do something..
    finally
       Free;
    end;

VCL is plenty of things like that as many of other real-world stuff.
Long live to WITH statements!

Anonymous said...

Malcolm Groves wrote an article about this a few years ago. It used to appear on his company's website (Madrigal Technologies) but I think that site is gone now.

I found it on his own home page, within an article called "Writing Solid Delphi Code" (http://www.malcolmgroves.com/stories/2004/04/05/writingSolidDelphiCode.html). Of course you should read the whole article, but the portion on the With statement starts about two thirds of the way through under a subtitle "Hard to see, the Dark Side is".

Anonymous said...

I always avoid using the With statement, and it's exactly for the reasons mentioned here.

Actually I even take it a step further, as I always use the Self identifier in front of references to object variables and properties.

It might clutter the code slightly, but at the same time it removes all doubt about which property or variable that is being referenced...

Is this an extreme practice? :)

Elling

Hallvard Vassbotn said...

Hi Elling

(Are you the Elling I think you are?) ;)

> Is this an extreme practice? :)

Yes ;)

Anonymous said...

Yes, I'm the Elling you think I am. :) Lars Fosdal pointed me towards your blog when I talked to him earlier today.

Regarding the Self identifier, I actually use it like this:

Self.Foo (An object variable or function)
Foo (A local variable or function)

I don't know whether it's a habit or a bad habit, but at least it makes it imediately clear where a variable or function is defined!



Copyright © 2004-2007 by Hallvard Vassbotn