テキストファイルを解析してジェネリックリストに配置する簡単なプログラムを書いています。
サンプルテキスト:
1,Joe,CA,58,2
2,Matt,TX,63,5
時々、ファイル内のデータが欠落しているエラーが発生する可能性があります
1,Joe,CA,58 // missing one number
2,Matt,TX,63,5
そのようなエラーを処理するためにcatch
ステートメントを記述しました。
私のソフトウェア原則の質問:
Catchステートメントは他のビジネスロジックを処理するために利用すべきではないと言われました。このcatchステートメントで、エラーフォルダーと、問題の原因となったデータを含むファイルを作成しています。これらのステートメントはどこに再配置する必要がありますか?以下のcatch
ステートメントを参照してください。
public class CustomerData
{
public int CustomerId { get; set; }
public string CustomerName { get; set; }
public string CustomerState { get; set; }
public int ProductId { get; set; }
public int QuantityBought { get; set; }
}
public List<CustomerData> GetCustomer(string filename)
{
List<CustomerData> customerdata = new List<CustomerData>();
string CustomerBase = filename;
String fileToLoad = String.Format(CustomerBase);
using (StreamReader r = new StreamReader(fileToLoad))
{
string line;
while ((line = r.ReadLine()) != null)
{
string[] parts = line.Split(',');
// Skip the column names row
if (parts[0] == "id") continue;
try
{
CustomerData dbp = new CustomerData
{
CustomerId = Convert.ToInt32(parts[0]),
CustomerName = parts[1],
CustomerState = parts[2],
ProductId = Convert.ToInt32(parts[3]),
QuantityBought = Convert.ToInt32(parts[4]),
};
customerdata.Add(dbp);
}
catch
{
Console.WriteLine("Parse Error!");
string ErrorFolderPath = @"C:\Users\Desktop\Parsefile\ErrorFile";
string ErrorFile = System.IO.Path.Combine(ErrorFolderPath, Path.GetFileName(filename));
bool FolderExists = System.IO.Directory.Exists(ErrorFolderPath);
if (!FolderExists)
System.IO.Directory.CreateDirectory(ErrorFolderPath);
bool ErrorFileExists = System.IO.File.Exists(ErrorFile);
if (!ErrorFileExists)
System.IO.File.Create(ErrorFile);
using (TextWriter tw = new StreamWriter(ErrorFile))
{
tw.WriteLine(line);
}
}
}
}
return customerdata;
}
catch
ブロック内であまり複雑なことをしたくないのは、catchブロックの例外が発生し、制御のフローが混乱し始めるためです(特に、catch内にtryブロックをネストする場合)。代わりに、例外ロジックをトリガーするために必要なものだけを保存し、そのロジックをフラグに依存するif
ブロックに配置します。
あなたの例は少し長いですが、ここではそれを簡単に表しています:
void DoSomething()
{
try
{
DoSomethingErrorProne();
}
catch
{
DoSomethingComplicatedToHandleAProblem(); //bad
}
}
ロジックを抽出する方法は次のとおりです。
public void DoSomething()
{
var ok = TryDoSomethingInternal();
if (!ok) DoSomethingComplicatedToHandleAProblem();
}
private bool TryDoSomethingInternal();
{
try
{
DoSomethingErrorProne();
return true;
}
catch
{
return false;
}
}
最初の質問は、「なぜロギングライブラリを使用しないのか」です。これは、catchステートメントで実行しているすべてのことを処理します。
基本的に、コードは次の疑似コードに要約されます。
try
{
CustomerData dbp = new CustomerData
{
CustomerId = Convert.ToInt32(parts[0]),
CustomerName = parts[1],
CustomerState = parts[2],
ProductId = Convert.ToInt32(parts[3]),
QuantityBought = Convert.ToInt32(parts[4]),
};
customerdata.Add(dbp);
}
catch (Exception e)
{
WriteErrorLine(line);
}
それは許容できます。特に、100万行のファイルのすべての行でエラーが発生しない場合。つまり、その処理は例外的なものに対するものです。
通常、アプリケーションロジックの処理に例外を使用しないことに関するアドバイスは、ほぼすべての行で発生する例外に依存しています。なぜそれが悪いのかを示すために、たとえば、CustomerState
が2文字の状態コード、時には完全な状態、時には整数IDであったとしましょう。また、enum
に評価するための状態が必要だったとしましょう。これは物事が厄介になることができる場所です。現在、解析エラーが発生するのは例外的な状態ではありません。物事を整理するために、関数で構文解析が行われる場合があります。
private State ParseState(string raw)
{
try
{
return (State) Enum.Parse(typeof(State), raw);
}
catch (ArgumentException) {}
try
{
return abbreviations[raw];
}
catch (KeyNotFoundException) {}
try
{
return (State) int.Parse(raw);
}
catch (ParseException) {}
throw IllegalArgumentException("Could not parse state string");
}
次に、これらの例外のそれぞれがログに記録され、ファイルが状態を表す方法を1行ずつ切り替えた場合についても想像してください。スローされる多くの例外があり、最悪の場合、各行に4つの例外があります。これらの例外のスタックトレースを入力するには、かなりの時間がかかります。これがたまにしか呼び出されなかったとしても、それほど悪くはないかもしれませんが、数千行でもファイルを解析する場合、これらの例外の影響はさらに複雑になります。
この場合、渡された文字列に対していくつかのテストを行い、どのパスが最も可能性が高いかを確認してから、メソッドのTryParse
またはTryGetValue
バリアントを使用する方がより正確であり、パフォーマンスも優れています。例外をスローしません。