我没有任何昂贵的组件库,例如DevExpress或TMS Components等,所以我无法查看源代码以了解大多数组件如何管理错误处理。
具体而言,我想知道的是组件开发人员应该尝试捕获多少错误和警告?在进行有意义的错误检查和让开发人员使用您的组件变得太容易之间是否存在平衡?
以下是使用几种方案的示例:
请注意,这些内容直接来自组件来源(仅供参考)
procedure TMyComponent.AddFromFile(FileName: string);
begin
FBitmap.LoadFromFile(FileName);
end;
或
procedure TMyComponent.AddFromFile(FileName: string);
begin
if FileExists(FileName) then
begin
FBitmap.LoadFromFile(FileName);
end
else
raise Exception.Create(FileName + ' does not exist.');
end;
最后两个是在运行时使用组件的实例:
procedure TForm1.FormCreate(Sender: TObject);
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end;
或
procedure TForm1.FormCreate(Sender: TObject);
begin
if FileExists('D:\Test.bmp') then
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end
else
raise Exception.Create('D:\Test.bmp does not exist.');
end;
我想这归结为谁应该错误检查并处理什么?组件开发人员是负责处理这些类型的检查还是组件的用户?
在我写这篇文章时,我相信组件开发人员和用户都应该处理这样的检查,但我不确定,所以我正在寻找开发人员普遍的共识。
感谢。
答案 0 :(得分:2)
要回答您的具体问题:
具体而言,我想知道的是组件开发人员应该尝试捕获多少错误和警告?在进行有意义的错误检查和让开发人员使用您的组件变得太容易之间是否存在平衡?
关于异常处理的一般规则是,您应该只捕获您知道如何处理的异常,并让其他人传播到可能知道如何处理它的更高代码。如果组件内部引发异常,则组件需要决定是否:
在内部处理该特定异常并优雅地转移到其他内容而不会通知调用者。
重新抛出异常(可能会对其进行调整),或者重新抛出一个全新的异常,以便调用者能够识别并处理该特定故障(如果需要)。
忽略异常(根本不抓住它),让它按原样传播。
如果组件使用的API返回错误代码而不是引发异常,则组件也需要决定如何处理它。是否忽略错误并继续前进,或者引发异常以使其更明显。
在您的特定示例中,我更喜欢以下方法:
type
EMyComponentAddError = class(Exception)
private
FFileName: String;
begin
constructor CreateWithFileName(const AFileName: string);
property FileName: string read FFileName;
end;
constructor EMyComponentAddError.CreateWithFileName(const AFileName: string);
begin
inherited CreateFmt('Unable to add file: %s', [AFileName]);
FFileName := AFileName;
end;
procedure TMyComponent.AddFromFile(FileName: string);
begin
try
FBitmap.LoadFromFile(FileName);
except
Exception.RaiseOuterException(EMyComponentAddError.CreateWithFileName(FileName));
end;
end;
这允许您的组件识别出错误,根据需要对其进行操作,并仍然向调用者报告特定于组件的信息,而不会丢失导致实际失败的原始错误。如果调用者对细节感兴趣,它可以捕获异常,查看其InnerException
属性,访问自定义属性(如果存在)等。
例如:
procedure TForm1.FormCreate(Sender: TObject);
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end;
让我们假设MyComponent1.AddFromFile('D:\Test.bmp');
失败。默认的异常处理程序将捕获它并显示一条弹出消息:
Unable to add file: D:\Test.bmp
有用但很少有细节,因为它可能因各种原因而失败。也许文件无法打开,但为什么?不存在与未获得许可?也许文件被打开但已损坏?也许内存无法分配?等等。
如果需要,调用者可以捕获它并显示更多有用信息(不需要 - 组件提供信息,调用者决定是否使用它):
procedure TForm1.FormCreate(Sender: TObject);
begin
try
MyComponent1.AddFromFile('D:\Test.bmp');
except
on E: EMyComponentAddError do
begin
ShowMessage('There was a problem adding a file:'+sLineBreak+E.FileName+sLineBreak+sLineBreak+E.InnerException.Message);
Sysutils.Abort;
end;
end;
end;
或者:
procedure TForm1.FormCreate(Sender: TObject);
begin
try
MyComponent1.AddFromFile('D:\Test.bmp');
except
on E: EMyComponentAddError do
begin
raise Exception.CreateFmt('There was a problem adding a file:'#10'%s'#10#10'%s', [E.FileName, E.InnerException.Message]);
end;
end;
end;
其中任何一个都会显示:
There was a problem adding a file:
D:\Test.bmp
The file was not found
答案 1 :(得分:1)
<强>组件强>
procedure TMyComponent.AddFromFile(FileName: string);
begin
FBitmap.LoadFromFile(FileName);
end;
这就是你所需要的。如果位图对象无法加载文件,无论出于何种原因,它都会引发异常。让该异常传播给代码的使用者。
尝试测试文件是否存在真的没有意义。如果文件存在且它不是位图文件怎么办?如果文件存在,该位图文件是什么,但磁盘有一个duff扇区并且文件读取失败?如果您尝试检查所有错误情况,则只需重复LoadFromFile
方法已经执行的检查。
无法从外部检查某些错误情况。只能通过读取文件在某种程度上变得明显的错误无法从外部进行合理的检查。
过度热心,重复错误检查的一个非常常见的后果是,您最终会遇到在应该没有错误的情况下产生错误的代码。如果您的错误检查错误,您最终可能会报告在您让底层代码运行时不会发生的错误。
<强>消费强>
procedure TForm1.FormCreate(Sender: TObject);
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end;
此时决定更加困难。我通常希望以下问题成为决定的驱动因素:
文件不存在是一个预期的,合理的事件吗?
如果该问题的答案是肯定的,那么您应该考虑在FormCreate
方法中处理异常。同样,测试FileExists()
只捕获一种失败模式,尽管是常见的失败模式。也许您应该使用try/except
块来捕获错误。
如果问题的答案为否,请让错误传播。
也就是说,您还应该考虑是否要从表单的OnCreate
事件处理程序中抛出异常。这可能是完全合理的,但你肯定不希望这样做。
答案 2 :(得分:1)
正如大卫所说,我们只需要这个
procedure TMyComponent.AddFromFile(FileName: string);
begin
FBitmap.LoadFromFile(FileName);
end;
这将检查
现在它取决于应用程序,这对应用程序有多重要。如果此TForm1
是Application.MainForm
,则您在创建过程中未捕获的每个异常都将终止该应用程序。这有时是一种有效的行为。
非常重要的是,如果没有
,应用程序就无法运行procedure TForm1.Form1Create(Sender:TObject);
begin
MyComponent.AddFromFile( 'D:\Test.bmp' );
end;
或包装用户友好消息的异常
procedure TForm1.Form1Create(Sender:TObject);
begin
try
MyComponent.AddFromFile( 'D:\Test.bmp' );
except
on E: Exception do
raise Exception.Create( 'Sorry, I cannot run, because of: ' + E.Message );
end;
end;
非常重要,但我们有一个后退来处理这个问题,也许
procedure TForm1.Form1Create(Sender:TObject);
var
LBitmapFiles : TStringList;
LBitmapIdx : Integer;
LBitmapLoaded : Boolean;
LErrorStore : TStringList;
begin
LBitmapFiles := nil;
LErrorStore := nil;
try
LBitmapFiles := TStringList.Create;
LErrorStore := TStringList.Create;
LBitmapFiles.Add( 'D:\Test.bmp' );
LBitmapFiles.Add( 'D:\Fallback.bmp' );
LBitmapLoaded := False;
while not LBitmapLoaded and ( LBitmapIdx < LBitmapFiles.Count ) do
try
MyComponent.AddFromFile( LBitmapFiles[LBitmapIdx] );
LBitmapLoaded := True;
except
on E: Exception do
begin
LErrorStore.Add( LBitmapFiles[LBitmapIdx] + ': ' + E.Message );
Inc( LBitmapIdx );
end;
end;
if not LBitmapLoaded then
raise Exception.Create( 'Sorry, I cannot run, because of: ' + LErrorStore.Text );
finally
LErrorStore.Free;
LBitmapFiles.Free;
end;
end;
还有其他可能的回退,这也取决于应用程序(f.i.将虚拟位图设置到组件)以使应用程序正常工作。
不重要,如果我们没有图像......我们没有图像,谁在乎
procedure TForm1.Form1Create(Sender:TObject);
const
CBitmapFile = 'D:\Test.bmp';
begin
// check, if there is a file
if FileExists( CBitmapFile ) then
try
MyComponent.AddFromFile( CBitmapFile );
except
on E: Exception do
begin
// Maybe log the exception
SomeLogger.Log( E );
// Maybe set some extra parameters for the application to know, this has failed
RunningWithoutBitmap();
end;
end
else
// Maybe set some extra parameters for the application to know, this has failed
RunningWithoutBitmap();
end;