4

I have a function like this, that I would like to refactor

   function Myfunction(sUrl, sFile: String) : Boolean;
    var
      GetData : TFileStream;
    begin
      Result := False;
      //if the line below fails, I get an unhandled exception
      GetData := TFileStream.Create(sFile, fmOpenWrite or fmCreate);
      try        
        try
          IdHTTP.Get(sUrl, GetData);
          Result := (IdHTTP.ResponseCode = 200);
        except
          on E: Exception do begin
            MessageBox(0, PChar(E.message), 'Niðurhala skrá', MB_ICONERROR or MB_OK);
          end;
        end;
      finally
        GetData.Free;
      end;
    end;

    Procedure SomeOtherCode;
     Begin
        //How can I best defend against the unhandled exception above
        //unless the call to the function is packed in a try .. except block
        //the code jumps skips the if statement an goes to next 
        //exception block on the stack
        if MyFunction('http://domain.com/file.html', 'c:\folder\file.html') then
            ShowMessage('Got the file')
         else
            ShowMessage('Error !');
        End
     end;

Question:

Please refer to the comment within the procedure SomeOtherCode above.

Best Regards

menjaraz
  • 7,551
  • 4
  • 41
  • 81
Sigurdur
  • 417
  • 4
  • 10

6 Answers6

10

Just wrap the code where you want to trap exceptions in a try..except block:

function MyFunction(...): Boolean;
var
  Stream: TFileStream;
begin
  Result := False;
  try
    Stream := TFileStream.Create(...);
    try
      // more code
      Result := ...
    finally
      Stream.Free;
    end;
  except
    // handle exception
  end
end;
Ondrej Kelle
  • 36,941
  • 2
  • 65
  • 128
4

The whole point about exception handling is two-fold:

  • finally is for resource cleanup; you see this often in business logic
  • except is for reacting on specific exception (and getting rid of state logic through function results and intermediate variables); you hardly see it in business logic

In your case:

Myfunction should not return a Boolean, not contain an except block, and not perform a MessageBox but just let the exceptions propagate.
SomeOtherCode should contain the except block and tell the user what went wrong.

Example:

procedure Myfunction(sUrl, sFile: String);
var
  GetData: TFileStream;
begin
  Result := False;
  //if the line below fails, I get an unhandled exception
  GetData := TFileStream.Create(sFile, fmOpenWrite or fmCreate);
  try        
    IdHTTP.Get(sUrl, GetData);
    if (IdHTTP.ResponseCode <> 200) <> then
      raise Exception.CreateFmt('Download of %s failed, return code %d', [sURl, IdHTTP.ResponseCode]);
  finally
    GetData.Free;
  end;
end;

procedure SomeOtherCode:
begin
  try
    MyFunction('http://domain.com/file.html', 'c:\folder\file.html');
  except
    on E: Exception do begin
      MessageBox(0, PChar(E.message), 'Niðurhala skrá', MB_ICONERROR or MB_OK);
    end;
  end;
end;

Now the code is much cleaner:

  • no more UI in your business logic
  • one place where your except is being handled
  • all failures are handled equally (cannot create file, download failure)

Good luck with this.

--jeroen

Jeroen Wiert Pluimers
  • 23,965
  • 9
  • 74
  • 154
3

If you want your function to show messages to the user and return false on any failure, code it as follows:

function Myfunction(sUrl, sFile: String) : Boolean;
var
  GetData : TFileStream;
begin
  Result := False;
  try
    //if the line below fails, I get an unhandled exception
    GetData := TFileStream.Create(sFile, fmOpenWrite or fmCreate);
    try        
      try
        IdHTTP.Get(sUrl, GetData);
        Result := (IdHTTP.ResponseCode = 200);
      except
        on E: Exception do begin
          MessageBox(0, PChar(E.message), 'Niðurhala skrá', MB_ICONERROR or MB_OK);
        end;
      end;
    finally
      GetData.Free;
    end;
  except
    // you can handle specific exceptions (like file creation errors) or any exception here
  end;
end;

Warning IMHO this design is mixing business logic (such as get a resource/file from the Internet and save it to a file) and user interface logic (such as showing messages to the user in case of errors).

In general, is a better approach to separate business from UI logic, because your code is reusable.

For example you might want to re-factor as this:

function DownloadToAFile(const sUrl, sFile: string): boolean;
var
  GetData : TFileStream;
begin
  GetData := TFileStream.Create(sFile, fmOpenWrite or fmCreate);
  try        
    IdHTTP.Get(sUrl, GetData);
    Result := (IdHTTP.ResponseCode = 200);
  finally
    GetData.Free;
  end;
end;

function UIDownloadToAFile(const sUrl, sFile: string): boolean;
begin
  try
    Result := DownloadToAFile(sURL, sFile);
  except
    on E: EIDException do //IndyError
      MessageBox(0, PChar(E.message), 'Internet Error', MB_ICONERROR or MB_OK);
    on E: EFileCreateError do //just can't remember the extact class name for this error
      MessageBox(0, PChar(E.message), 'File create Error', MB_ICONERROR or MB_OK);
  end;
end;

procedure SomeOtherCode:
begin
  if UIDownloadToAFile('http://domain.com/file.html', 'c:\folder\file.html') then
    ShowMessage('Got the file')
   else
     ShowMessage('Error !');
end;

Tomorrow, if you're writing a service, or a DataSnap module, you're free to use the DownloadToAFile or maybe to write a new ServiceDownloadToAFile wich in turns writes errors to a log or windows events, or maybe send a email notifying the HostAdmin about it.

jachguate
  • 16,976
  • 3
  • 57
  • 98
2

One solution which is quite popular is to avoid 'success' or 'failure' return values completely. Instead of a function, use a procedure and handle failures using exceptions instead:

procedure Download(sUrl, sFile: String);

and then

try
  Download ('http://domain.com/file.html', 'c:\folder\file.html');
  ShowMessage('Got the file')
except
  on E:Exxx do 
  begin
    // handle exception
    ShowMessage('Error !');
  end
end;

This has also the effect that nobody can invoke the function and silently ignore the return value.

mjn
  • 36,362
  • 28
  • 176
  • 378
1

For some reason most people misuse except-finally combination. Correct sequence is

try 
  // allocate resource here
  try 
  finally
    // free resource here
  end;
except
  // handle exception here
end;

This lets you catch exceptions in constructor and destructor.

Eugene Mayevski 'Callback
  • 45,135
  • 8
  • 71
  • 121
  • 6
    I disagree with you... there's some cases where you want to handle exceptions only if constructor succeeds, but you want to let the exception fly if the constructor by itself fails. I think there's no a general recipe to order or nest try/finally or try/except blocks, **it always depends** on what you want. – jachguate Nov 11 '10 at 17:06
  • @jachguate No it doens't. In except-finally sequence you never catch errors in cleanup code. Also, if catch catches everything, finally is simply useless. – Eugene Mayevski 'Callback Nov 11 '10 at 17:17
  • @Eugene: you're free to handle any exception in the except clause, but sometimes you handle some specific exceptions, sometimes you re-raise the exceptions... so finally clause is not useless _by definition_ after a except clause. – jachguate Nov 11 '10 at 17:21
  • @jachguate If you need to put finally after except, this means that it's place is inside of try/except block. think again about this. – Eugene Mayevski 'Callback Nov 11 '10 at 17:39
  • 4
    Indeed, `finally` is useless if `except` catches everything. But if `except` catches everything, then you're already doing things wrong because there's *no way* you could possibly resolve every possible error condition. In Sigurdur's case, the `TFileStream` constructor call clearly needs a `try`-`except` around it, but that doesn't mean that it should be the *same* `try`-`except` block that wraps the *use* of that stream object. The exceptions might need to be handled differently. – Rob Kennedy Nov 11 '10 at 18:08
  • @Rob looks like nobody does programming nowadays. if except catches everything, this doesn't mean except do_nothing end;. This means that error handling is provided for any error that might happen there. – Eugene Mayevski 'Callback Nov 11 '10 at 18:11
  • 2
    *Handling* an exception means that whatever triggered the error is no longer a problem. If the source of the error hasn't actually been fixed, then the `except` block should re-throw the exception. But you're not talking about re-throwing. For `EOutOfMemory`, you must be freeing up memory. For `EAccessViolation`, you must be correcting invalid addresses. For `EPrinterOnFire`, you must be calling the fire department. Those are all bigger problems than this simple file-downloading function should handle. They should bubble up to the caller, and so, it's correct to have a `finally` block outside. – Rob Kennedy Nov 11 '10 at 19:31
  • @Eugene: I can think on a bunch of valid uses for try/except inside a try finally, for example... create a TIDHTTP, and free it inside the finally part of the outer try. But, you may want to try different mirrors for download a file if you experience any connection error with the first. You can even make different retries to the same server in a timely fashion until a max-retries count is reached before letting the exception to go away (just a dumb example, sure there are a lot of better ones. Think again about this. ;) – jachguate Nov 11 '10 at 20:27
  • @Rob Finally doesn't just impact on exceptions, it also executes when you try to `exit` or `break` through a finally. – David Heffernan Dec 15 '10 at 16:30
  • @David, I assume your comment is in regard to my statement that "`finally` is useless if `except` catches everything," where I was agreeing with Eugene and disagreeing with Jachguate. If your point was that the statement is too broad, then you're right; thank you for pointing it out. But if that wasn't what you meant, then that simple statement of fact wasn't enough for me to grasp your point; please elaborate. – Rob Kennedy Dec 15 '10 at 17:59
  • @Rob You interpret me correctly. It's a little nuanced but even the Delphi documentation isn't very helpful. The documentation of Try/Finally talks exclusively about exceptions. If you want to know about how exit/break/continue (I forgot about continue) interact with Try/Finally then you have to know to look at their topics. I also don't know about goto which is another candidate for interaction with Try/Finally, but since I never use it I'm not too bothered about that gap in my knowledge. – David Heffernan Dec 15 '10 at 19:17
0

You should use only one try and get in this your all function code.

Svisstack
  • 16,203
  • 6
  • 66
  • 100