After reading my "How to separate interface code and functional code (based on Delphi/VCL)", someone mentioned a question, which is how to handle errors in server-side classes.
In function-based structures, we generally use function return values to indicate whether the function was successfully executed and to give information such as error types. So there will be code in the following form:
RetVal := SomeFunctionToOpenFile();
if RetVal = E_SUCCESSED then
...
else if RetVal = E_FILENOTFOUND then
...
else if RetVal = E_FILEFORMATERR then
...
else then
...
It is very common to use a method that returns an error code, but there are two problems with using such a method:
1. It creates a long and complicated branch structure (a large number of if or case statements), making the control process complicated.
2. There may be errors that have not been handled (if the function caller does not determine the return value)
Exceptions are an object-oriented solution for error handling. It can report errors, but what you need to know is that the exception is not raised because of the error, but simply because raise is used.
In Object Pascal, the raised reserved word is used to throw exceptions. At any time (even if no error occurs), raise will cause an exception to occur.
Exceptions can cause the code to return immediately from the point where the exception occurs, thereby protecting the sensitive code below from being executed. There is no difference between returning from a function through an exception and returning from a function normally (executing to the end of the function or executing Exit) for the function itself that throws the exception. The difference is that at the caller's end, after returning through an exception, execution rights will be captured by the caller's try...except blocks (if they exist). If there is no try...except block at the caller, subsequent statements will not continue to be executed, but will return to the higher-level caller until a try...except block that can handle the exception is found. After the exception is handled, the statements after the try...except block will continue to be executed, and control is left in the layer that handles the exception. When the exception handler feels that the handling of the exception is not complete enough, it needs the higher-level caller to continue processing. It can re-throw the exception (use a simple raise;) and transfer control to the higher-level caller.
If there is no try...except block preset at all, the final exception will be caught by the outermost try...except block of the VCL that encapsulates the entire program.
Therefore, there will be no unhandled exceptions, in other words, there will be no unhandled errors (although errors and exceptions are not equated). This is also the advantage of the exception mechanism over using methods that return error codes. In addition, after the exception is thrown, the direction of the control process is very clear and will not cause the process to lose control.
To give an example of how exceptions work, suppose we want to open a file in a specific format:
First define two exception classes (inherited from Exception)
EFileNotFound = class(Exception);
EFileFormatErr = class(Exception);
Suppose there is a button on Form1, and pressing the button opens the file:
PRocedure TForm1.Button1Click(Sender: TObject);
begin
try
ToOpenFile();
except
on EFileNotFound do
ShowMessage('Sorry, I can't find the file');
onEFileFormatErr do
ShowMessage('Sorry, the file is not the one I want');
on E:Exception do
ShowMessage(E.Message);
end;
end;
And the function to open the file:
procedure ToOpenFile;
varRetVal:Integer;
begin
//Some code to openfile
RetVal := -1; //open failed
if RetVal = 0 then //success
Exit
else if RetVal = -1 then
Raise EFileNotFound.Create('File not found')
else if RetVal = -2 then
Raise EFileFormatErr.Create('File format error')
else //other error
Raise Exception.Create('Unknown error');
end;
In the program, TForm1.Button1Click calls ToOpenFile and presets try...except for handling exceptions that may be thrown by ToOpenFile. Of course, the exception handling code of TForm1.Button1Click can also be simplified:
procedure TForm1.Button1Click(Sender: TObject);
begin
try
ToOpenFile();
except
ShowMessage('Open file failed');
end;
end;
Using exceptions solves the problems of using methods that return error codes. Of course, using exceptions is not without cost. Exceptions will increase the burden on the program, so abusing exceptions is not advisable. There is a big difference between writing a few try...except and writing thousands of try...except. In the words of Charlie Calverts: "You should use try...except blocks when it seems useful. But try not to get too excited about this technique."
In addition, Object Pascal introduces a unique try...finally structure. I said before that there is no difference between returning from a function through an exception and returning from a function normally. Therefore, the local objects in the stack in the function will be automatically released, but the objects in the heap will not. However, Object Pascal's object model is based on references, which exist in the heap, not the stack. Therefore, sometimes we need to clean up some local object resources before returning from a function through an exception. try...finally solves this problem.
I rewrote the above ToOpenFile code, this time using some resources during the ToOpenFile process, and releasing these resources after the exception occurs (or does not occur) before returning from the function:
procedure ToOpenFile;
varRetVal: Integer;
Stream: TStream;
begin
//Some code to openfile
Stream := TStream.Create;
RetVal := -1; //open failed
try
if RetVal = 0 then //success
Exit
else if RetVal = -1 then
Raise EFileNotFound.Create('File not found')
else if RetVal = -2 then
Raise EFileFormatErr.Create('File format error')
else //other error
Raise Exception.Create('Unknown error');
finally
Stream.Free;
end;
end;
Stepping through the above code, we can see that even when the value of RetVal is 0, after executing Exit, the code in finally will still be executed and then return from the function. This ensures the correct release of local resources.
The purposes and usage scenarios of try...except and try...finally are different, and many beginners confuse them. The following is some personal knowledge of the author: try...except is generally used by the caller to capture exceptions thrown by the called functions and handle them. And try...finally is generally used for the function that throws the exception to perform some resource cleanup work.
Object-oriented programming provides an error handling solution called "exception". Used wisely, it will benefit our work and can significantly improve the quality of the code we write.
Nicrosoft ([email protected]) 2001.7.25
Original source: Sunistudio Document (http://www.sunistudio.com/asp/sunidoc.asp)