【问题标题】:Delphi -make a function available to other forms beside mainformDelphi - 使功能可用于除主窗体之外的其他窗体
【发布时间】:2014-01-23 20:26:29
【问题描述】:

我使用一个函数将应用程序错误记录到一个文件中。我将它放在我的主表单上。如果出现错误,我如何告诉我的其他表单使用此函数?碰巧我在其他表单上将此添加到我的查询中:

......
try
ClientDataSet1.Execute;
ClientDataSet1.Close;
except       
on E:Exception do begin
ShowMessage('Error : ' + E.Message);
LogError(E);  
....

我的日志错误(E);不被识别(给出错误)。尝试添加功能:procedure LogError(E:Exception);用于公共用途,但不起作用。

【问题讨论】:

  • 最佳实践是将日志功能与 GUI 分离。将它放在一个单独的单元中,只需包含该单元即可从任何地方访问它。

标签: delphi exception logging delphi-xe4


【解决方案1】:

结构化异常处理

我想指出,您的异常处理方法是非常乏味和浪费的编码。更不用说相当危险了。当有更实用的方法时,您不希望在代码中使用单独的处理程序来记录异常。

幸运的是,Delphi 提供了使这更容易的方法。首先是关于结构化异常处理的一些背景知识......大多数开发人员都会错误地通过编写如下代码来尝试处理错误:

try
  //Do Something
except
  //Show Error
  //Log Error
end;

上述代码的问题在于它吞下了错误。即使用户看到错误消息,错误本身也对程序的其余部分隐藏。这可能会导致程序的其他部分因为错误而做他们不应该做的事情。

想象一个调用以下例程的程序:LoadAccount; ApplyDiscount; ProcessPayment;。但是第一个例程会引发错误。程序员(错误地认为他们很勤奋)决定如上所述“处理”。 问题是接下来的两个例程仍将被调用好像没有任何问题!这可能意味着对错误帐户应用了折扣并处理了付款!

关键是结构化异常处理使我们免于这些麻烦(只要我们不破坏它的工作方式)。如果LoadAccount 例程没有尝试“处理”异常,那么当应用程序处于“异常状态”时,代码将简单地跳出例程,直到找到 finally / 除了处理程序。

这非常适合事件驱动程序。
假设用户单击了一个按钮,该按钮将导致您的程序启动一个多步骤任务:使用循环、调用子方法、创建对象等。如果 任何事情 出错,您想放弃整个任务并等待下一个输入。你不想固执地继续尝试完成任务;因为您要么会遇到更多错误,要么更糟:以后的更改会做错事,因为之前的必要步骤没有成功完成。

轻松记录错误

如前所述,如果您只是不理会异常,错误将“冒泡”到外部异常处理程序。在标准的 GUI 应用程序中,这将是默认的 Application 异常处理程序。

其实在这个阶段,没什么特别的:如果在前面描述的按钮点击任务的中间引发异常,默认的异常处理程序显示一个给用户的错误信息。唯一缺少的是日志记录。幸运的是,Delphi 使您可以轻松拦截默认的Application 异常处理程序。因此,您可以提供自己的实现,并根据需要记录错误。

您需要做的就是:

  • 使用TExceptionEvent 签名编写一个方法。
    例如。 procedure MyHandleException(ASender: TObject; E: Exception);.
  • 然后使用:Application.OnException := MyHandleException; 分配您的自定义处理程序

常规日志记录

当然,这仅涵盖异常记录。如果您想进行任何其他临时日志记录,您希望能够重用代码。这基本上要求您将日志记录代码移动到一个单独的单元中,所有其他单元可以根据需要调用。

所以把这些东西放在一起,你可能会做如下事情:

TLogger = class
public
  procedure WriteToLog(S: string);  //Performs actual logging of given string
  procedure HandleException(ASender: TObject; E: Exception); //Calls WriteToLog
end;

那么你可以在你的程序单元中进行如下设置:

begin
  Logger := TLogger.Create(...);
  Application.OnException := Logger.HandleException;
  Logger.WriteToLog('Custom exception handler has been assigned.');
  //Rest of application startup code
end;

最后的想法

我提到如果发生意外的异常,你想放弃当前的任务。但是,如果您已经做了一些由于错误而现在应该撤消的事情,会发生什么。然后您只需按如下方式实现它:

try
  //Do something
except
  //Undo
  raise; //NOTE: more often than not, one should re-raise an 
         //exception so that callers are aware of the error.
end;

请注意,this 是异常处理程序的主要用途。是的,它们也可以用来吞下异常。但他们应该在异常真正解决的情况下吞下异常。

【讨论】:

  • 确实读得很好......所以如果我理解正确,在我的情况下,我只需要添加到公共子句:Procedure LogError(const message: string);在我需要记录的每个表格上?只需将表单添加到使用子句...但是 Craig,我对吞下错误的想法与您相同。所以基本上我需要为我需要运行的每个查询编写 TRY/EXCEPT/RAISE,我的意思是这将是一个正确的方法?一直在寻找这样的查询示例,但到目前为止还没有找到。想一想,这些块与事务相比更好......
  • @user3181689 不,你误会我了。 (1) 您只需为整个应用程序设置一次日志记录。每个单独的表格都不需要任何额外的东西。 (2) 不,每个查询都不需要 try/except/raise。您只需要该模式来执行由于异常而需要撤消的任何特定操作。即使这样,一些设计选择也可以为您提供更简单的选择。例如。如果您的应用有一个全局 Connection:确保任何 Db 更新都会启动 Db Transaction,然后您的自定义异常处理程序总是可以回滚任何打开的 Db Transaction。
【解决方案2】:

虽然 Delphi 是面向对象的语言,但它也可以处理非 OO 实体。

想想,你的主要形式是哪个形式的属性?

答案是主窗体不是任何其他窗体的属性,它是一个全局对象。应该对您的功能做同样的事情。

unit Main;

interface uses .......;

type TMainForm = class (TForm)....
....
     end;


Procedure LogError(const message: string);

implementation uses .....;

Procedure LogError(const message: string);
begin
...
end;

(**********)

procedure TMainForm.FormCreate(...);
begin
....
end;

...
end.

现在更好的选择是将 LogError 与任何形式完全分离。

Unit MyLogger;

interface uses ....; // but does not uses Main or any other form

procedure SetupLog(....); 
// to initialize it and change any settings you may wish
// maybe you would choose to save messages to file
// or use Windows debug interface - OutputDebugString
// or to a network SysLog server
// or just WriteLn it to a text window
// or to some TMemo in a graphic window
// or to file AND to a memo - that also might make sense.

// just keep it extensible

Procedure LogWarning(const message: string);
Procedure LogError(const message: string);

implementation uses ...;

function GenericLoggingSink(.....);
begin
...
end;

Procedure LogError(const message: string);
begin
  GenericLoggingSink( Log_Message_Type_Error, message, ... );
end;

Procedure LogWarning(const message: string);
begin
  GenericLoggingSink( Log_Message_Type_Warning, message, ... );
end;

你的主窗体应该只使用这个单元和这个函数,就像你的应用程序中的所有其他窗体一样。

作为第三部分,我建议您思考您希望从日志记录过程中得到什么。 实际上这样做只对非常简单的日志记录需求有意义。非常简单。

否则,只需采用任何现有的 Delphi 日志框架并利用其中实现的大量功能。例如,您可以只记录您要编写的任何类的对象吗?

创建自己的日志库的唯一原因是“我的程序非常简单,它只需要最原始的日志记录。即兴创作我自己的系统比复制粘贴库初始化和设置要快示例到现成的库”。

Log4Delphi 和 Log4D 是著名的 FLOSS 库,尽管它们看起来有些废弃。也许没有什么可以扩展的了。这些是最旧的,但也有一些较新的 FLOSS 库,一个简单的例子是 http://blog.synopse.info/post/2013/02/03/Log-to-the-console

如果有的话,你可以阅读他们的文字并向他们学习。

还有一些商业库,如Is there a way to do normal logging with EureakLog? 中列出的那些库,并且 Delphi 本身附带了有限版本的 CodeSite 日志框架。

CnWizards 和 GExperts 等 Delphi IDE 增强功能也带有简化的面向调试的日志接口。

如果您敢的话,Google 会为您提供更多选择 :-)

【讨论】:

  • 两个答案都很棒....今天学到了一些东西...不知道哪个答案更好...希望我可以分开...:)
  • @user3181689 如果你有勇气,我建议你尝试掌握erlang.org/download/armstrong_thesis_2003.pdf 虽然系统完全不同,但你可能会事后对 Craig 试图告诉你的内容有所了解。不是特定的 Delphi 关键字,而是一个概念、总体规划等。在上面的链接中,我们过去在 Delphi 中看到的对象和函数是独立的线程,几乎是程序。我们在该链接中看到的函数引发异常是程序崩溃和死亡。
  • 如果 Delphi 和更多编程,这种对比可能会帮助您减少思考,而这正是您现在所缺乏的。编程就是寻找共同的模式并进行抽象,拉出括号的共同乘数。但正如我在开场白中所说,你确实缺乏它。非常。您查看细节,不能只退后 3 步来查看全貌。你真的需要训练它比其他任何东西都多。现在又是那篇文章
  • 那篇文章再说一遍:使用与 Delphi 非常不同的系统,您可能很难找到关于错误的相同概念:任何遇到这种情况的活动实体(函数或程序 - 没有区别)未计划的(错误)应该停止运行(退出 fn,使应用程序崩溃)并警告它的上层链有问题。实体可能会尝试在其能力级别上修复 IT 已更改的内容,但不会更多。上层程序/功能应该一直崩溃,直到弹出一个足够有能力的程序,因此它被计划为正常计划情况下的任务失败。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-05
  • 1970-01-01
  • 1970-01-01
  • 2011-05-13
  • 2010-10-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多