About a year ago, I wrote about Delphi: you should avoid the `with` statement as it makes your code less future proof. Then I already tweeted I would follow up. Time to do it now (:
Besides my first post, these links inspired me most:
- Is Delphi “with” keyword a bad practice? – Stack Overflow.
- Why should I not use “with” in Delphi? – Stack Overflow.
- If With is Considered Harmful, What About Double With?.
- Movie #19 – Hate With a Passion.
“The with keyword. The most hideous, dangerous, blow-your-own-feet-off feature in the language.” by Verity Stob
Posts about the with statement usually cause a stir: people either like or dislike it with passion.
Starting with some history and examples, this posts lists a few DOs and DON’Ts when using the with statement, shows advantages and drawbacks, and shows you tools to eliminate with statements.
Then
No wonder people are passionate about the with statement: it has been a feature ever present in Pascal. It was introduced with reason, but it also has drawbacks. Though briefly mentioned in the 1983 “Standard Pascal User Reference Manual” Paperback by Doug Cooper (Look Inside the Standard Pascal User Reference Manual: Doug Cooper: 9780393301212: Amazon.com: Books, then search for “with”) it was a very important addition at that time: 1970s and 1980s.
Remember Turbo Pascal 1.0 in 1983 being one of the first products having an IDE, but not even a debugger?
Basically back then, your best editor could do almost nothing so anything that saved typing lots of text was a big plus.
And with would potentially save a lot of text.
“Dull” text that replaced the full access path of relatively simple situations: record, or maybe nested records.
Back then you had very simple scope: records only held storage. Methods were relatively straight forward global functions or procedures.
Now
Fast forward to now:
- classes, namespaces, units that make scoping a truckload more complex
- excellent IDEs available for almost any programming language. Syntax highlighting, code completion, context sensitive editors, and more advanced features save you most of the typing.
To get a feel of the DOs and DON’Ts about the Delphi with statement.
Let’s start with this simple VCL example:
procedure TForm1.Edit1Click(Sender: TObject); begin with Edit1 do begin ShowMessage(Format('%s: %s', [Name, Caption])); end; end;
You might think that this code shows something like “Edit1: Edit1″. But it shows “Edit1: Form1″, as a TEdit does not have a Caption property (it has a Text property), but TForm has a Caption property.
procedure TForm1.Button1Click(Sender: TObject); begin with Button1 do begin ShowMessage(Format('%s: %s', [Name, Caption])); // put a breakpoint here end; end;
Red lights should start flashing when you add multiple clauses into one with statement, as that usually widens up the scope so much that in stead of benefiting from it, you will feel pain as If With is Considered Harmful, What About Double With? shows: where does Enabled
belong to?
with LMargins, GlassFrame do begin if Enabled then begin if not SheetOfGlass then begin cxLeftWidth := Left; cxRightWidth := Right;
Sometimes, the use of with is sign of a need for refactoring, for instance shown in Is Delphi “with” keyword a bad practice?:
procedure TMyForm.AddButtonClick(Sender: TObject); begin with LongNameDataModule do begin LongNameTable1.Insert; LongNameTable1_Field1.Value := 'some value'; LongNameTable1_Field2.Value := LongNameTable2_LongNameField1.Value; LongNameTable1_Field3.Value := LongNameTable3_LongNameField1.Value; LongNameTable1_Field4.Value := LongNameTable4_LongNameField1.Value; LongNameTable1.Post; end end;
The above code has a big dependency on the LongTableDataModule and is business logic. So this business logic should be refactored out of the form into the data module. Not doing so violates Law of Demeter, which is basically about Loose Coupling.
procedure TLongNameDataModule.AddToLongNameTable1(const NewField1Value: string); begin LongNameTable1.Insert; LongNameTable1_Field1.Value := NewField1Value; LongNameTable1_Field2.Value := LongNameTable2_LongNameField1.Value; LongNameTable1_Field3.Value := LongNameTable3_LongNameField1.Value; LongNameTable1_Field4.Value := LongNameTable4_LongNameField1.Value; LongNameTable1.Post; end;
Then call it from the form like this:
procedure TMyForm.AddButtonClick(Sender: TObject); begin LongNameDataModule.AddToLongNameTable1('some value'); end;
This effectively gets rid of the with statement, and makes the code more maintainable: kill two birds with one stone.
One of the few places I still use with
is like here (thanks
with TMyForm.Create(nil) do try ShowModal(); finally Free(); end;
and here (thanks
procedure ActionOnUpdate(Sender: TObject) begin with Sender as TAction do Enabled := Something; end;
More drawbacks
Put a breakpoint on the indicated line, then observe the debugger takes the Name and Caption properties from Form1, not of Button1. You can find another example of this debugger behaviour here.
with also can prevent some refactorings (both the stock Delphi ones, and ones available through for instance ModelMaker Code Explorer and Castallia).
Alternatives
Both VB6 and Oxygene have alternatives for the with statement.
In VB6, you have to prepend all the usage with a dot (.) which used in Delphi code would make your code look like this:
begin with MagicalFrame.Label1 do begin if .Font.Color = clBlue then begin .Font.Color := clRed; .Caption := 'Red'; end else begin .Font.Color := clBlue; .Caption := 'Blue'; end; end; end;
In Oxygene, you can introduce a temporary variable in a with statement.
Eliminating with
I know of two tools to eliminate the with statement.
- Bruce McGee indicated One of the Castalia refactoring tools is named “Eliminate ‘WITH’” in delphi – Tool to refactor “with” blocks – Stack Overflow.
- As of ModelMaker Code Explorer 9, there is a with statement converter.
I have a lot of experience with the latter, but Castallia should work just as good.
–jeroen
Filed under: Delphi, Delphi 1, Delphi 2005, Delphi 2006, Delphi 2007, Delphi 2009, Delphi 2010, Delphi 3, Delphi 4, Delphi 5, Delphi 6, Delphi 7, Delphi 8, Delphi x64, Delphi XE, Delphi XE2, Delphi XE3, Delphi XE4, Development, Software Development