Reading about Delphi's Exit
statement (see here for instance), I cannot ignore that writing about it every author feels the duty to give a piece of advice, for example:
Warning : use with caution - jumping is a concept at odds with structured coding - it makes code maintenance difficult.
Now, I'm coming from C and C++ in Unix and I'm familiar with re-entrancy issues, but honestly I cannot figure out why in Delphi returning from a function before it reaches its natural end should be evil.
Unless every function and procedure in Delphi is considered as re-entrant.
What am I missing?
In Delphi, the Exit procedure immediately passes control away from the current procedure. If the current procedure is the main program, Exit causes the program to terminate. Exit causes the calling procedure to continue with the statement after the point at which the procedure was called.
To perform a normal termination of a Delphi application, call the Terminate method on the global Application object. If the application does not use a unit that provides an Application object, call the Exit procedure from the main Program block.
There are two schools of thought.
One school of thought says that functions should have a single exit point. Many many coding standards enforce that as a rule. The motivation for this comes from the hard experience of maintaining spaghetti code full of large functions with gotos, multiple exits and so on.
The other school of thought says that spaghetti code is bad but one should be pragmatic and judge coding styles on their merits rather than following dogmatic rules. For example, many programmers feel that guard clauses are much preferable to the deeply indented functions that arise when you refrain from using exit
. As an illustration consider the following example from Martin Fowler's excellent refactoring catalog: Replace Nested Conditional with Guard Clauses.
Fundamentally it all comes down to personal preference. I personally encourage the use of guard clauses, but refrain from wild use of exit in long procedures.
You're missing that it doesn't say "don't use this" or "Exit is evil"; it says "use it carefully." And it says that it can make maintenance difficult. For example, if you've got a large method and there's an line like this somewhere in the middle, you might miss it entirely:
if aLocalObject.CheckSomeValue(aParameter) <> RIGHT_VALUE then Exit;
And yeah, I actually have seen stuff like that before, unfortunately. :(
A lot of the problems can be mitigated with a few rules of thumb:
Exit
(and Break
and Continue
) statements on their own line. It makes them harder to miss.try/finally
blocks so that Exit
ing out of a procedure is safe to do without causing leaks or corruption.In addition to David's and Mason's superb answers, I would like to share my personal Exit
usage favorite. Let's call it, quite contrary to "safe guard", a "last resort provider". ;)
The basic idea:
function Search(List: TList; Item: TObject): Integer;
begin
for Result := 0 to List.Count - 1 do
if List[Result] = Item then
Exit;
Result := -1;
end;
Other more realistic examples (from this answer and this answer):
const
Order: array[0..6] of String = ('B', 'C', 'A', 'D', 'G', 'F', 'E');
function GetStringOrder(const S: String; CaseSensitive: Boolean): Integer;
begin
for Result := 0 to Length(Order) - 1 do
if (CaseSensitive and (CompareStr(Order[Result], S) = 0)) or
(not CaseSensitive and (CompareText(Order[Result], S) = 0)) then
Exit;
Result := Length(Order);
end;
function FindControlAtPos(Window: TWinControl; const ScreenPos: TPoint): TControl;
var
I: Integer;
C: TControl;
begin
for I := Window.ControlCount - 1 downto 0 do
begin
C := Window.Controls[I];
if C.Visible and PtInRect(C.ClientRect, C.ScreenToClient(ScreenPos)) then
begin
if C is TWinControl then
Result := FindControlAtPos(TWinControl(C), ScreenPos)
else
Result := C;
Exit;
end;
end;
Result := Window;
end;
And concluding with a quote from Delphi's help on the compiler error message FOR-Loop variable '<element>' may be undefined after loop:
You can only rely on the final value of a for loop control variable if the loop is left with a goto or exit statement.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With