一般业务对象实践(和异常错误 - redux)

General Business Object Practices (and exception errors - redux)

提问人:stephenbayer 提问时间:10/25/2008 最后编辑:Communitystephenbayer 更新时间:1/16/2015 访问量:656

问:

最近,我写了一篇关于与我合作的开发人员没有正确使用 try catch 块的帖子,并且不幸使用了 try...在危急情况下捕获块并一起忽略异常错误。导致我非常心痛。以下是他们执行此操作的数千个代码部分之一的示例(一些代码被遗漏了,这些代码并不重要:

public void AddLocations(BOLocation objBllLocations)
{
    try
    {
        dbManager.Open();
        if (objBllLocations.StateID != 0)
        {
             // about 20 Paramters added to dbManager here
        }
        else
        {
           // about 19 Paramters added here
        }
        dbManager.ExecuteNonQuery(CommandType.StoredProcedure, "ULOCATIONS.AddLocations");
    }
    catch (Exception ex)
    {
    }
    finally
    {
        dbManager.Dispose();
    }
}

在我看来,这绝对是讨论,并且在发生某些潜在问题时不会通知用户。我知道很多人说 OOP 是邪恶的,添加多层会增加代码行数和程序的复杂性,从而导致代码维护可能出现的问题。就我个人而言,我的大部分编程背景在这个领域都采取了几乎相同的方法。下面我列出了我通常在这种情况下编码方式的基本结构,在我的职业生涯中,我一直在使用多种语言进行编码,但这个特定的代码是用 C# 编写的。但是下面的代码是我如何使用对象的一个很好的基本想法,它似乎对我有用,但由于这是一些相当智能的编程地雷的良好来源,我想知道我是否应该重新评估我使用了这么多年的技术。主要是因为,在接下来的几周里,我将深入研究外包开发人员的不太好的代码,并修改大量代码。我想尽可能地做到这一点。对不起,代码引用太长了。

// *******************************************************************************************
/// <summary>
/// Summary description for BaseBusinessObject
/// </summary>
/// <remarks>
/// Base Class allowing me to do basic function on a Busines Object
/// </remarks>
public class BaseBusinessObject : Object, System.Runtime.Serialization.ISerializable
{
    public enum DBCode
    {   DBUnknownError,
        DBNotSaved,
        DBOK
    }

    // private fields, public properties
    public int m_id = -1;
    public int ID { get { return m_id; } set { m_id = value; } }
    private int m_errorCode = 0;
    public int ErrorCode { get { return m_errorCode; } set { m_errorCode = value; } }
    private string m_errorMsg = "";
    public string ErrorMessage { get { return m_errorMsg; } set { m_errorMsg = value; } }
    private Exception m_LastException = null;
    public Exception LastException { get { return m_LastException; } set { m_LastException = value;} }

    //Constructors
    public BaseBusinessObject()
    {
        Initialize();
    }
    public BaseBusinessObject(int iID)
    {
        Initialize();
        FillByID(iID);
    }
    // methods
    protected void Initialize()
    {
        Clear();
        Object_OnInit();
        // Other Initializable code here
    }
    public void ClearErrors()
    {
        m_errorCode  = 0; m_errorMsg = ""; m_LastException = null;
    }

    void System.Runtime.Serialization.ISerializable.GetObjectData(
         System.Runtime.Serialization.SerializationInfo info, 
        System.Runtime.Serialization.StreamingContext context)
    {
      //Serialization code for Object must be implemented here
    }
    // overrideable methods
    protected virtual void Object_OnInit()     
    {
        // User can override to add additional initialization stuff. 
    }
    public virtual BaseBusinessObject FillByID(int iID)
    {
        throw new NotImplementedException("method FillByID Must be implemented");
    }
    public virtual void Clear()
    {
        throw new NotImplementedException("method Clear Must be implemented");
    }
    public virtual DBCode Save()
    {
        throw new NotImplementedException("method Save Must be implemented");
    }
}
// *******************************************************************************************
/// <summary>
/// Example Class that might be based off of a Base Business Object
/// </summary>
/// <remarks>
/// Class for holding all the information about a Customer
/// </remarks>
public class BLLCustomer : BaseBusinessObject
{
    // ***************************************
    // put field members here other than the ID
    private string m_name = "";
    public string Name { get { return m_name; } set { m_name = value; } }
    public override void Clear()
    {
        m_id = -1;
        m_name = "";
    }
    public override BaseBusinessObject FillByID(int iID)
    {
        Clear();
        try
        {
            // usually accessing a DataLayerObject, 
            //to select a database record
        }
        catch (Exception Ex)
        {
            Clear();
            LastException = Ex;
            // I can have many different exception, this is usually an enum
            ErrorCode = 3;
            ErrorMessage = "Customer couldn't be loaded";
        }
        return this;
    }
    public override DBCode Save()
    {
        DBCode ret = DBCode.DBUnknownError;
        try
        {
            // usually accessing a DataLayerObject, 
            //to save a database record
            ret = DBCode.DBOK;
        }
        catch (Exception Ex)
        {
            LastException = Ex;
            // I can have many different exception, this is usually an enum
            // i do not usually use just a General Exeption
            ErrorCode = 3;
            ErrorMessage = "some really weird error happened, customer not saved";
            ret = DBCode.DBNotSaved;
        }
        return ret;
    }
}
// *******************************************************************************************
// Example of how it's used on an asp page.. 
    protected void Page_Load(object sender, EventArgs e)
    {
        // Simplifying this a bit, normally, I'd use something like, 
        // using some sort of static "factory" method
        // BaseObject.NewBusinessObject(typeof(BLLCustomer)).FillByID(34);
        BLLCustomer cust = ((BLLCustomer)new BLLCustomer()).FillByID(34);
        if (cust.ErrorCode != 0)
        {
            // There was an error.. Error message is in 
            //cust.ErrorMessage
            // some sort of internal error code is in
            //cust.ErrorCode

            // Give the users some sort of message through and asp:Label.. 
            // probably based off of cust.ErrorMessage
            //log can be handled in the data, business layer... or whatever
            lab.ErrorText = cust.ErrorMessage;
        }
        else
        {
            // continue using the object, to fill in text boxes, 
            // literals or whatever. 
            this.labID = cust.ID.toString();
            this.labCompName = cust.Name;
        }
    }

归根结底,我的问题是,我是否将多层和继承类的事情复杂化了,或者我的旧概念仍然运行良好且稳定?现在有没有更好的方法来完成这些事情?我是否应该像同事同事开发人员建议的那样,从页面后面的 asp.net 页代码进行直接的 SQL 调用(尽管最后一个解决方案让我觉得恶心),而不是通过业务对象和数据层(数据层未显示,但基本上包含所有存储的 proc 调用)。是的,另一位开发人员确实问过我,为什么我要努力分层,当你可以直接在 *.aspx.cs 代码后面的页面中输入你需要的东西时,然后我就可以享受超过 1k 行代码的乐趣。这里有什么建议?

C# 错误处理 对象

评论


答:

1赞 jonnii 10/25/2008 #1

您是否考虑过使用像 NHibernate 这样的 ORM?重新发明轮子是没有意义的。

对我来说,这是一种代码味:

BLLCustomer cust = ((BLLCustomer)new BLLCustomer()).FillByID(34);

括号太多了!

我发现在像 C# 这样的语言中使用活动记录模式总是以眼泪告终,因为它很难进行单元测试。

评论

0赞 stephenbayer 10/27/2008
对不起。:(我当时做了很多lisp编程,它的影响在其他语言中也得到了体现。
0赞 Magnus Lindhe 10/25/2008 #2

为什么不直接在Page_Load事件中捕获异常呢?您可能期望并知道如何处理某些异常,其他异常应由全局异常处理程序处理。

1赞 Luke 10/25/2008 #3

从第一段代码到第二段代码的跳跃是巨大的。是否需要复杂的业务对象层将取决于相关应用程序的大小。不过,至少我们的策略是将异常记录在处理异常的地方。如何向用户展示取决于您,但拥有日志是必不可少的,以便开发人员可以在必要时获得更多信息。

0赞 DancesWithBamboo 10/25/2008 #4

我的经验法则是只捕获我可以处理的错误,或者给用户一些有用的东西,这样如果他们再做一次,它很可能对他们有用。我捕获数据库异常;但只是为了在有关正在使用的数据的错误上添加更多信息;然后我重新扔掉它。一般来说,处理错误的最佳方法是不要在任何地方捕获它们,而是在 UI 堆栈的顶部捕获它们。只需有一个页面来处理错误并使用 global.asax 路由到它,几乎可以处理所有情况。一起使用状态代码绝对是过时的。它是 COM 的残余。

0赞 Simon P 10/25/2008 #5

是否可以使用抽象基类而不是具体类?这将强制在开发时实现方法,而不是运行时异常。

这里最好的评论是来自舞蹈,你应该只处理你可以从中恢复的异常。抓住别人并重新投掷是最好的方法(尽管我认为它很少这样做)。另外,请确保它们已被记录...... :)

0赞 chrissie1 10/25/2008 #6

你的错误处理方式似乎已经过时了。只需创建一个新的执行并从执行中继承,这样您至少可以拥有调用堆栈。然后你应该用nlog或log4net之类的东西来记录。这是 2008 年,所以使用泛型。这样你将不得不做更少的投射。

并且像以前有人说的那样使用 ORM。不要试图重新发明轮子。

评论

0赞 David Robbins 10/25/2008
你说得好,但 2008 年的评论充其量是讽刺性的。