一般的なビジネスオブジェクトプラクティス(および例外エラー-redux)
-
04-07-2019 - |
質問
最近、について投稿しました私が取り組んでいる開発者は、try catchブロックを適切に使用していない、および重大な状況でtry ... catchブロックを不幸にも使用しており、例外エラーをすべて無視しています。私に大きな心痛を引き起こします。以下は、彼らがこれを行った数千のコードセクションのうちの1つの例です(特に重要ではない一部のコードは省略しました:
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呼び出しを行う必要があります(最後のソリューションでは気分が悪くなりますが)示されていますが、基本的にはすべてのストアドプロシージャコールを保持しています)。ええ、別の開発者は、ページの背後にある* .aspx.csコードに必要なものを直接入力することができ、1k行以上のコードの喜びを得ることができるのに、なぜ私は物事を階層化する努力をするのか尋ねました後ろに。ここで何かアドバイスはありますか?
解決
NHibernateのようなORMの使用を検討しましたか?車輪を再発明する意味はありません。
これはコードの匂いです:
BLLCustomer cust = ((BLLCustomer)new BLLCustomer()).FillByID(34);
括弧が多すぎます!
C#などの言語でアクティブなレコードパターンを使用すると、ユニットテストが難しいため、常に涙が出ることがわかりました。
他のヒント
コードの最初のビットから次のビットへのジャンプは巨大です。複雑なビジネスオブジェクトレイヤーが必要かどうかは、問題のアプリのサイズによって異なります。少なくとも私たちのポリシーでは、例外は処理された場所でログに記録されます。ユーザーにどのように提示するかはあなた次第ですが、必要に応じて開発者がより多くの情報を取得できるように、ログを保持することが不可欠です。
Page_Loadイベントで例外をキャッチしないのはなぜですか?あなたが期待し、対処方法を知っているかもしれないいくつかの例外、他の例外はグローバルな例外ハンドラーによって処理されるべきです。
私の経験則では、処理できるエラーのみをキャッチするか、ユーザーに何か有用なものを提供します。データベースの例外をキャッチします。ただし、使用されているデータに関するエラーにさらに情報を追加する場合のみ。それから私はそれを再投げます。一般的にエラーを処理するための最良の方法は、UIスタックの最上部を除き、どこでもエラーをキャッチしないことです。 1つのページでエラーを処理し、global.asaxを使用してそのページにルーティングするだけで、ほぼすべての状況を処理できます。ステータスコードを使用することは、間違いなくすべてのスタイルから外れています。 COMの名残です。
具象クラスの代わりに抽象基本クラスを使用することは可能ですか?これにより、ランタイム例外ではなく、開発時にメソッドの実装が強制されます。
ここで同意するのに最適なコメントは、ダンスからのものです。その時点で回復できる例外のみを処理する必要があります。他の人を捕まえて再投げるのが最良の方法です(私はめったに行われないと思いますが)。また、ログに記録されていることを確認してください。...:)
エラー処理の方法は非常に時代遅れのようです。新しい例外を作成し、少なくともコールスタックがあるように例外から継承します。次に、nlogやlog4netなどでログを記録する必要があります。そして、これは2008年なので、ジェネリックを使用します。そのようにキャストする必要はほとんどありません。
以前に誰かが言ったようにORMを使用します。車輪を再発明しようとしないでください。