My general perspective on this kind of problem is not that if statements are bad, it's that it's easier to debug data than it is to debug code.
Here's a non-trivial example from production code. This may look a little complicated at first blush, but at its core it's really simple: depending on the disposition code on a charge row, we need to perform an update to some of its related sentence rows. But we pick different sentence rows, and perform different kinds of updates on them, for different disposition codes.
This is a relatively simple example - there are only five disposition codes, two tests, and two types of updates. Even so, this is vastly simpler than what it replaced. Also, it's a lot easier to tell just from looking at the code that it does what the requirements say it should do, since the mappings in the code correspond to tables in the requirements document. (Before I wrote this code, I had to rewrite the requirements document so that this stuff was all defined in a table. The original code was a mess because the requirements were a mess too. Rewriting the requirements to make them clearer exposed bugs in the requirements, too.)
It's worth emphasizing that it's pretty easy to write a unit test that covers 100% of this code. It's also worth emphasizing that the complexity of this code scales linearly with the number of disposition codes, predicates, and updates that it supports; if case or if statements were used, it would scale exponentially.
/// <summary>
/// Update a sentence's status to Completed [401110]
/// </summary>
/// <param name="senRow"></param>
/// <param name="eventDate"></param>
private static void CompleteSentence(DataRow senRow, DateTime eventDate)
{
senRow.SetField("SenStatus", "401110");
senRow.SetField("SenStatusDate", eventDate);
}
/// <summary>
/// Update a sentence's status to Terminated [401120]
/// </summary>
/// <param name="senRow"></param>
/// <param name="eventDate"></param>
private static void TerminateSentence(DataRow senRow, DateTime eventDate)
{
senRow.SetField("SenStatus", "401120");
senRow.SetField("SenStatusDate", eventDate);
}
/// <summary>
/// Returns true if a sentence is a DEJ sentence.
/// </summary>
/// <param name="senRow"></param>
/// <returns></returns>
private static bool DEJSentence(DataRow senRow)
{
return Api.ParseCode(senRow.Field<string>("SenType")) == "431320";
}
/// <summary>
/// Returns true if a sentence is a Diversion sentence.
/// </summary>
/// <param name="senRow"></param>
/// <returns></returns>
private static bool DiversionSentence(DataRow senRow)
{
return Api.ParseCode(senRow.Field<string>("SenType")).StartsWith("43");
}
/// <summary>
/// These are predicates that test a sentence row to see if it should be updated
/// if it lives under a charge disposed with the specified disposition type.
///
/// For instance, if the PDDispositionCode is 413320, any DEJ sentence under the
/// charge should be updated.
/// </summary>
private static readonly Dictionary<string, Func<DataRow, bool>> PDSentenceTests =
new Dictionary<string, Func<DataRow, bool>>
{
{"411610", DiversionSentence}, // diversion successful
{"413320", DEJSentence}, // DEJ successful
{"442110", DiversionSentence}, // diversion unsuccessful
{"442111", DiversionSentence}, // diversion unsuccessful
{"442112", DiversionSentence}, // diversion unsuccessful
{"442120", DEJSentence} // DEJ unsuccessful
};
/// <summary>
/// These are the update actions that are applied to the sentence rows which pass the
/// sentence test for the specified disposition type.
///
/// For instance, if the PDDispositionCode is 442110, sentences that pass the sentence
/// test should be terminated.
/// </summary>
private static readonly Dictionary<string, Action<DataRow, DateTime>> PDSentenceUpdates =
new Dictionary<string, Action<DataRow, DateTime>>
{
{"411610", CompleteSentence}, // diversion successful (completed)
{"413320", CompleteSentence}, // DEJ successful (completed)
{"442110", TerminateSentence}, // diversion unsuccessful (terminated)
{"442111", TerminateSentence}, // diversion unsuccessful (terminated)
{"442112", TerminateSentence}, // diversion unsuccessful (terminated)
{"442120", TerminateSentence} // DEJ unsuccessful (terminated)
};
private void PDUpdateSentencesFromNewDisposition()
{
foreach (DataRow chargeRow in PDChargeRows
.Where(x => PDSentenceTests.ContainsKey(x.Field<string>("PDDispositionCode"))))
{
string disp = chargeRow.Field<string>("PDDispositionCode");
foreach (DataRow s in CHGRows[chargeRow]
.ChildRows("CAS-SUBCRM-CHG-SEN")
.Where(x => PDSentenceTests[disp](x)))
{
PDSentenceUpdates[disp](s, EventDate);
}
}
}