I hate to even say this, but it's not exactly hard to figure out how scope rules work. Last scope in wins. Always.
But human beings have this problem, which in psychology is called Chunk Theory. When you are presented with seven things to keep track of in your mind, one falls out, because your brain has about 6 local registers to keep things in. Imagine the simple identifier Foo
appears here:
with EXPRESSION1 do begin
with EXPRESSION2 do begin
Foo;
end;
end;
The above is for all intents identical to with EXPRESSION1, EXPRESSION2 do begin...
.
So let's see how simple rules like scope get too complicated to follow after a while. Let's imagine that we have the following levels of scope in some delphi application we're working in:
- The unit scope of every unit that is in your
use
clauses.
- Your own unit's implementation section scope (Unit-internals)
- The class scope (identifiers inside your current class if you are writing a method)
- The local procedure or method scope (var section in a procedure or function).
- The first with statement in the sample above.
- The second with statement in the sample above.
Update David has pointed out to me that I should mention that instead of 6 "scopes" above, we really have 5+x scopes, where x is the length of both your uses
clauses.
Now, the problem isn't the WITH statement being unclear, it's that human beings can easily get lost when you have 6 layers of lexical scoping going on, as above, and requires you not only to be aware of all places where Foo
is defined, because if you thought that the innermost With statement has something named Foo
defined, and it doesn't you get a rather nasty and hard to find bug in your code. And so, we have very smart, very capable people like Nick, saying very sensible things like "never use with". And I agree about 99% with Nick.
I also think that you could say that the enemy is not with, it's the tendency Delphi developers have to just keep growing their applications iteratively in a "RAD style" until they have become monstrosities. A unit which uses 200 other units is a mess, each of which uses 200 other units, is going to cause you more grief even than rampant abuse of WITH
statements.
WITH
is not the entire problem in bad code, it's just the most common bee in a Delphi developer's bonnet. Maybe it gets more attention than over-large uses-clauses, but over my entire career, dependency-hell and enormous-uses-clauses have done 100 times more to make life hard, than WITH
. So I think that WITH considered harmful
is over-done, and other things which ought to be considered more, are under considered.
A reasonable alternative to using with, is to use a single letter variable name (I know, go ahead and disagree), and avoid all scope ambiguity:
procedure NoWithPlease;
var
a:TSomething;
begin
a := Some.Long.Expression[x].That.You.Dont.Want.To.Repeat.Six.Times;
a.Foo := 'test';
a.Bar := 'test2'
end;
That is better, many would say, than this:
procedure WithPleasure;
begin
with Some.Long.Expression[x].That.You.Dont.Want.To.Repeat.Six.Times do begin
Foo := 'test';
Bar := 'test2'
end;
end;
Now I've been bitten by WITH
too many times to advocate its unrestricted use. But I also think there are times when it's actually better than doing the local variable stuff. It doesn't require that you declare a local variable, or determine the type of the expression. If delphi had type inference
(the auto
keyword in C++), then I would say, we could easily do without WITH
completely, but as it is, it is kind of a way to avoid statically creating a dependency on implementation types, and along with the ability to create readable sub-scopes, it can sometimes make your code easier to read, and sometimes make it worse, especially when there is more than one level of WITH
statements.
However, opinions on that matter vary, and like other programming topics, tends to turn into a bike-shed discussion, or worse yet, a holy war.
My suggested rules:
Avoid WITH
unless it makes your code better. Most places that you think you need a with, you don't, use a local variable instead.
Always avoid multi-level WITH
statements.