Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is Delphi's Exit statement dangerous? [closed]

Tags:

delphi

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?

like image 792
Federico Zancan Avatar asked Mar 23 '12 16:03

Federico Zancan


People also ask

What does exit do in Delphi?

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.

How do you end a procedure in Delphi?

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.


3 Answers

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.

like image 179
David Heffernan Avatar answered Nov 08 '22 07:11

David Heffernan


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:

  • Always put Exit (and Break and Continue) statements on their own line. It makes them harder to miss.
  • Favor small methods over larger ones, and break down large methods into smaller ones when reasonable. (WHEN REASONABLE! This is not an absolute rule, and applying it too zealously can make things worse instead of better. Keep Einstein's quote in mind: "Make everything as simple as possible, but not simpler.")
  • Learn to use try/finally blocks so that Exiting out of a procedure is safe to do without causing leaks or corruption.
like image 9
Mason Wheeler Avatar answered Nov 08 '22 07:11

Mason Wheeler


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.

like image 2
NGLN Avatar answered Nov 08 '22 08:11

NGLN