The Delphi Debate series continues with another topic that has been discussed back and forth for ages. This time, instead of a procedure or function in the RTL, we are talking about three reserved words: the with, goto, and label statements which pre-date Delphi--they are part of the core Pascal language itself!
with statement in Delphi is a shorthand for accessing methods and properties of an object. An argument for using it is that it shortens the length of a line of code thus making the code more readable. For example:
procedure TMyForm.UpdateInventoryItem(const NewQty: Integer); begin dmStoreInventoryData.tblUpdateItemForStore.Edit; dmStoreInventoryData.tblUpdateItemForStore.FieldByName('Qty').AsInteger := NewQty; dmStoreInventoryData.tblUpdateItemForStore.Post; end;
Can be rewritten like this:
procedure TMyForm.UpdateInventoryItem(const NewQty: Integer); begin with dmStoreInventoryData do begin tblUpdateItemForStore.Edit; tblUpdateItemForStore.FieldByName('Qty').AsInteger := NewQty; tblUpdateItemForStore.Post; end; end;
Furthermore, you can nest
procedure TMyForm.UpdateInventoryItem(const NewQty: Integer); begin with dmStoreInventoryData do begin with tblUpdateItemForStore do begin Edit; FieldByName('Qty').AsInteger := NewQty; Post; end; end; end;
Now, disregarding the fact we added four "scaffolding" lines, the three original lines look rather succinct; but even if you're the only programmer who deals with this code, there will come a point in time in the future where you question whether you're calling an
Edit method of the data module or the table object in the data module. Worse, what if you forget about this
with statement and add an
Post method to the data module? Without touching this code, you've just broken it!
There are much better ways of handling coding tasks like this. In this small example, experienced programmers would quickly point out that a table object should not be accessed outside the data module but a procedure should be created in the data module that deals with the local table object, thus reducing the object layer by one level and thus the "need" to use a
with statement. Of course, there are many other ways and this is just one example, but that's a topic for another day.
with statement is strongly frowned upon by experienced programmers in the Delphi community, and I agree--with rare exceptions.
Sometimes, I need to pop up a form to request some simple option from the user, returning a single item picked from a list (for example). So I create a dialog form, remove it's global variable and add a public class function like this:
class function TfrmFilterSKUs.GetSKUFilter: string; begin with TfrmFilterSKUs.Create(Application.MainForm) do try if ShowModal = mrOK then Result := lbCategories.Items[lbCategories.ItemIndex] else Result := EmptyStr; finally Free; end; end;
This is taken straight out of one of my projects and while I recommend against using
with on general principles, I would say that IF you're the only programmer maintaining the code and IF the section of code is very small and IF you never use more than one object in a
with and IF you never, ever nest them, then in this very limited use case, they are handy.
GOTO & LABEL
I am surprised--and horrified--to know that
goto still exists in the Delphi language. I had to look up the syntax because I honestly didn't even know how to use it--or its corresponding
goto statement makes an immediate jump of the execution point to another spot in the code identified by its pre-defined
label. It can jump into or out of a
for-loop, past an object creation statement, create unidentifiable infinite loops, and all sorts of other very bad things. One article says, "it's like a ticking time bomb."
Delphi's roots are in Pascal which was designed to "encourage good programming practices using structured programming" (quoted from Wikipedia) and I imagine part of that was to wean people off using line numbers and GOTO statements that were so prevalent in BASIC code, which was introduced six years earlier and quickly became the defacto programming language on many systems both large and small. So to include a way to perpetuate spaghetti code perplexes me. Perhaps it was unthinkable in the day to design a language without a way to jump directly to another location in code like you could in assembly, or perhaps Niklaus Worth wanted to provide a way that people could get productive right away if they had code they wanted to port to help make the language popular (I have nothing to back this up).
Whatever the reason, it can't be removed now without possibly breaking some old code (although maybe that type of code should be broken!).
I hope there's no doubt remaining where I stand on this: Don't use
Now I want to throw one more into the mix because it ties in very closely with
goto--but I'm not nearly as adamant against using it. The system procedure
Exit in Delphi is not a reserved word but acts like a
goto in a method with an implied
label at the end of the method; more simply, it exits from the current procedure or function. Similar to the
goto, it breaks out of standard control structures and can easily be missed in a long method. There are two redeeming qualities (in my opinion): first, it always exits the method--you don't have to look around for the
label to figure out where program execution goes; and second, it can't jump into the middle of another block of code.
From what I've seen, the most frequent use of
Exit is to check for some constraints and if something fails, simply skip processing the rest of the code in the procedure. For example if you're opening a database table and checking the connection but find it's not accessible, then just call
Exit rather than fall through a proper
else structure. This can simplify things by keeping control structure code out of the way of what the method is trying to do but isn't that big of an advantage if written well.
This question was posed on StackOverflow several years ago and there were some good points made in the answers. I find myself siding with those who prefer a single exit point by using control structures and avoiding arbitrary jumps, even if it's just an innocent call to the convenient
You missed two more goto like statements:
They are basically gotos with a hard coded label at the end and after a loop respectively. Unlike Exit I don't use them as they make it difficult to understand the flow of execution, in particular in nested loops.
As for not using goto at all: I have to admit that there is exactly one goto in my whole code base where I simply couldn't think of a better way to implement what I needed.
I briefly considered adding
continue to the conversation but felt they are part of the loop control structure and (IMHO) not as "bad" as the goto-like constructs. Also, looping code is much more concise so anything that breaks out will be more visible and you're unlikely to skip large sections of code like an
Exit at the top of a procedure can. Of course, these are generalities and cases could be made either way.
Thanks for mentioning this!
Did you know you can do this too?
with dmStoreInventoryData, tblUpdateItemForStore do
FieldByName('Qty').AsInteger := NewQty;
Note I say "can" but not "should" or indeed "ever".
Yes, I've seen that--and it's almost as bad as nesting
with statements! In fact, it's really a short-hand way of specifying nested