Doing maintenance on a project I came across code, which I find unnecessary hard to read and I wish to refactor, to improve readability.
The functionality is a long chain of actions that need to be performed sequentially. The next action should only be handled if the previous action was successful. If an action is not successful a corresponding message needs to be set. And the returned type is a Boolean. (successful true/false). Just like the return type of all the called actions.
Basically it comes down to something like this.
string m = String.Empty; // This is the (error)Message.
bool x = true; // By default the result is succesful.
x = a1();
if(x) {
x = a2();
}
else {
m = "message of failure a1";
return x;
}
if(x) {
x = a3();
}
else {
m = "message of failure a2";
return x;
}
//etcetera..etcetera...
if(x){
m = "Success...";
}
else{
m = "Failure...";
}
return x;
My question is: What is a better structure / pattern to handle this kind of logic?
Main goals are:
increase readability.
increase maintainability.
Please keep in mind that it is quite a large chain of actions that is being performed sequentially. (Thousands lines of code)
Make a list of action/message pairs:
class Activity {
public Func<bool> Action { get; set; }
public String FailureMessage { get; set; }
}
Activity[] actionChain = new[] {
new Activity { Action = A1, FaulureMessage = "a1 failed"}
, new Activity { Action = A2, FaulureMessage = "a2 failed"}
, new Activity { Action = A3, FaulureMessage = "a3 failed"}
};
A1..A3 are no-argument methods returning bool. If some of your actions take parameters, you can use lambda expressions for them:
Activity[] actionChain = new[] {
...
, new Activity { Action = () => An(arg1, arg2), FaulureMessage = "aN failed"}
, ...
};
Now you can go through the pairs, and stop at the first failure:
foreach (var a in actionChain) {
if (!a.Action()) {
m = a.FailureMessage;
return false;
}
}
m = "Success";
return true;
#dasblinkenlight was faster... however a similar solution using yield instead of an array:
public string Evaluate()
{
foreach (var customAction in EnumerateActions())
{
if (!customAction.Execute())
return customAction.Error;
}
return "Success...";
}
public IEnumerable<CustomAction> EnumerateActions()
{
yield return new CustomAction(a1, "Error for A1");
yield return new CustomAction(a2, "Error for A2");
...
}
public class CustomAction
{
public Func<bool> Execute { get; }
public string Error { get; }
public CustomAction(Func<bool> action, string error)
{
Execute = action;
Error = error;
}
}
Related
Well, I would like to do my own benchmarking system like spark in Minecraft (https://github.com/lucko/spark):
I'm using Harmony lib (https://github.com/pardeike/Harmony) which allows me to interact/modify methods and allows me to add a Prefix/Postfix on each call that will help me out with this stack.
The basic structure has something similar to (https://github.com/pardeike/Harmony/issues/355):
[HarmonyPatch]
class MyPatches
{
static IEnumerable<MethodBase> TargetMethods()
{
return AccessTools.GetTypesFromAssembly(Assembly.GetExecutingAssembly())
.SelectMany(type => type.GetMethods())
.Where(method => method.ReturnType != typeof(void) && method.Name.StartsWith("Do"));
}
static void Prefix(out Stopwatch __state, MethodBase __originalMethod)
{
__state = Stopwatch.StartNew();
// ...
}
static void Postfix(Stopwatch __state, MethodBase __originalMethod)
{
__state.Stop();
// ....
}
}
The problem here is that the __originalMethod doesn't take care if it was called from A or B.
So for example, we had patched string.Join method. And the we call from A or B, where A or B, is the full callstack of this method.
So first, we need to assign a ID to this call, and we need to create a Tree-based structure (which is hard to serialize later), from here (https://stackoverflow.com/a/36649069/3286975):
public class TreeModel : Tree<TreeModel>
{
public int ID { get; set; }
public TreeModel() { }
public TreeModel(TreeModel parent) : base(parent) { }
}
public class Tree<T> where T : Tree<T>
{
protected Tree() : this(null) { }
protected Tree(T parent)
{
Parent=parent;
Children=new List<T>();
if(parent!=null)
{
parent.Children.Add(this as T);
}
}
public T Parent { get; set; }
public List<T> Children { get; set; }
public bool IsRoot { get { return Parent==null; } }
public T Root { get { return IsRoot?this as T:Parent.Root; } }
public T RecursiveFind(Predicate<T> check)
{
if(check(this as T)) return this as T;
foreach(var item in Children)
{
var result=item.RecursiveFind(check);
if(result!=null)
{
return result;
}
}
return null;
}
}
Now, the thing is that we need to fill the Tree as long as we iterate all the method and instructions got from Harmony. Forget about Harmony for a second, I will explain only two facts about it.
The lib allows you first to get all patched methods through IEnumerable<MethodBase> TargetMethods() so, you have the Assembly X passed through reflection and filtered all methods that are allowed to be patched (some of them broke Unity, so I decided to skip methods from UnityEngine., UnityEditor. and System.* namespaces).
And we have also the ReadMethodBody method (https://harmony.pardeike.net/api/HarmonyLib.PatchProcessor.html#HarmonyLib_PatchProcessor_ReadMethodBody_System_Reflection_MethodBase_) from a given MethodBase it returns all IL stack instructions.
So we can start to iterate over and over in order to get all instructions and fill the entire tree. This is what I wrote last night:
internal static class BenchmarkEnumerator
{
internal static Dictionary<MethodBase, int> Mappings { get; } = new Dictionary<MethodBase, int>();
internal static Dictionary<int, TreeModel> TreeIDs { get; } = new Dictionary<int, TreeModel>();
internal static Dictionary<MethodBase, BenchmarkTreeModel> TreeMappings { get; } = new Dictionary<MethodBase, BenchmarkTreeModel>();
private static HashSet<int> IDUsed { get; } = new HashSet<int>();
public static int GetID(this MethodBase method)
{
return GetID(method, out _);
}
public static int GetID(this MethodBase method, out bool contains)
{
// A > X = X1
// B > X = X2
if (!Mappings.ContainsKey(method))
{
var id = Mappings.Count;
Mappings.Add(method, Mappings.Count);
IDUsed.Add(id);
contains = false;
return id;
}
contains = true;
return Mappings[method];
}
public static int GetFreeID()
{
int id;
Random rnd = new Random();
do
{
id = rnd.Next();
} while (IDUsed.Contains(id));
IDUsed.Add(id);
return id;
}
public static BenchmarkCall GetCall(int id)
{
return TreeIDs[id]?.Call;
}
public static BenchmarkCall GetCall(this MethodBase method)
{
return TreeIDs[Mappings[method]]?.Call;
}
}
The BenchmarkEnumerator class allow us to differentiate between A or B, but it doesn't care about the full hierarchy, only from the parent MethodBase itself, so I need to write something complex to take in care of the full call stack, which I said I have a problem to understand.
Then we have the TargetMethods:
private static IEnumerable<MethodBase> TargetMethods()
{
Model = new BenchmarkTreeModel();
var sw = Stopwatch.StartNew();
//int i = 0;
return Filter.GetTargetMethods(method =>
{
try
{
var instructions = PatchProcessor.ReadMethodBody(method);
var i = method.GetID(out var contains);
var tree = new TreeModel
{
ID = i
};
if (contains)
{
//var lastId = i;
i = GetFreeID();
tree.ID = i;
tree.FillMethodName($"{method.GetMethodSignature()}_{i}"); // TODO: Check this
tree.Parent = null;
tree.Children = TreeMappings[method].Forest.First().Children; // ??
//DictionaryHelper.AddOrAppend(TreeMappings, method, tree);
TreeMappings[method].Forest.Add(tree);
TreeIDs.Add(i, tree);
Model.Forest.Add(tree);
// UNIT TESTING: All contained methods at this point will have a parent.
// string.Join is being added as a method by a instruction, so when we try to patch it, it will have already a reference on the dictionary
// Here, we check if the method was already added by a instruction CALL
// Logic: If the method is already contained by the mapping dictionary
// then, we will exit adding a new that will have the same childs but a new ID
return false;
}
TreeIDs.Add(i, tree);
tree.FillMethodName($"{method.GetMethodSignature()}_{i}"); // TODO: Check this
foreach (var pair in instructions)
{
var opcode = pair.Key;
if (opcode != OpCodes.Call || opcode != OpCodes.Callvirt) continue;
var childMethod = (MethodBase)pair.Value;
var id = childMethod.GetID(out var _contains);
var subTree = new TreeModel(tree)
{
ID = id
};
if (_contains)
{
id = GetFreeID();
subTree.ID = id;
subTree.FillMethodName($"{childMethod.GetMethodSignature()}_{id}"); // TODO: Check this
subTree.Parent = TreeIDs[i];
subTree.Children = TreeMappings[childMethod].Forest.First().Children;
TreeIDs.Add(id, subTree);
continue;
}
TreeIDs.Add(id, subTree);
subTree.FillMethodName($"{childMethod.GetMethodSignature()}_{id}");
tree.Children.Add(subTree);
TreeMappings.Add(childMethod, new BenchmarkTreeModel());
TreeMappings[childMethod].Forest.Add(subTree);
}
TreeMappings.Add(method, new BenchmarkTreeModel());
TreeMappings[method].Forest.Add(tree);
Model.Forest.Add(tree);
return true;
//var treeModel = new TreeModel();
}
catch (Exception ex)
{
//Debug.LogException(new Exception(method.GetMethodSignature(), ex));
return false;
}
}, sw);
//return methods;
}
The GetMethodSignature is something like:
public static string GetMethodSignature(this MethodBase method)
{
if (method == null) return null;
return method.DeclaringType == null ? method.Name : $"{method.DeclaringType.FullName}.{method.Name}";
}
I think I'll replace it with the MethodBase.ToString instead (what do you think?)
Also, we have the BenchmarkCall class which allow us to take in care how many times the call was done and how many time it has spent at all:
[Serializable]
public class BenchmarkCall
{
public string Method { get; set; }
public double SpentMilliseconds { get; set; }
public long SpentTicks { get; set; }
public double MinSpentMs { get; set; } = double.MaxValue;
public double MaxSpentMs { get; set; } = double.MinValue;
public long MinSpentTicks { get; set; } = long.MaxValue;
public long MaxSpentTicks { get; set; } = long.MinValue;
public double AvgMs => SpentMilliseconds / TimesCalled;
public double AvgTicks => SpentTicks / (double)TimesCalled;
public BenchmarkCall()
{
}
public BenchmarkCall(MethodBase method)
{
Method = method.GetMethodSignature();
}
public override string ToString()
{
if (TimesCalled > 0)
return "BenchmarkCall{\n" +
$"Ticks[SpentTicks={SpentTicks},MinTicks={MinSpentTicks},MaxTicks={MaxSpentTicks},AvgTicks={AvgTicks:F2}]\n" +
$"Ms[SpentMs={SpentMilliseconds:F2},MinMs={MinSpentMs:F2},MaxMs={MaxSpentMs:F2},AvgMs={AvgMs:F2}]\n" +
"}";
return "BenchmarkCall{}";
}
}
}
So I think that my next movement will be to differentiate between X method being called from A or B (Xa or Xb) taking care of the full hierarchy (which I'm not sure how to do) instead of the parent method that calls it, maybe the code I wrote has some to do it with it, but I'm not sure (last night I was so tired, so I didn't code it taking care those facts), build up a list of method signatures with different IDs, and then fill up the tree, ID 1 is Xa and ID 2 is Xb (where I have problems also filling up the tree).
Also I'll need to use the Transpiler in order to alter all code instructions, so if a method has:
void method() {
X1();
X2();
}
We will need to add 2 methods (like prefix/postfix) to measure each instruction call:
void method() {
Start(1);
X1();
End(1);
Start(2);
X2();
End(2);
}
This will be a hard task, but I hope somebody could guide me with this out.
I have a few items -- let's just call them itemA, itemB, itemC, and itemD -- and I would like to validate each of them using a validation method I have written.
The validation method return type and signature is as follows:
public async Task<ValidationMessage> ValidateItem(MyClass item);
The ValidationMessage is a simple class:
public class ValidationMessage
{
public bool Success { get; set; }
public string ErrorMessage { get; set; }
}
To validate each item, currently I have the following code:
ValidationMessage result = new ValidationMessage();
result = await this.ValidateItem(itemA);
if (!result.Success)
{
return result;
}
result = await this.ValidateItem(itemB);
if (!result.Success)
{
return result;
}
result = await this.ValidateItem(itemC);
if (!result.Success)
{
return result;
}
result = await this.ValidateItem(itemD);
return result;
As you can see, as soon as one of the items fails the validation method (meaning result.Success == false), I return and do not continue with validating the rest of them.
I think it's kind of tedious/ugly to have the repeated assignments to result and the repeated if statements. I was hoping there is some existing c# class/construct (perhaps LINQ can help) to write this more concisely. I made up the following to demonstrate what I'm sort of thinking:
ValidationMessage result = new ValidationMessage();
result = await this.ValidateItem(itemA).ContinueConditional(
(r => r.Success) => await this.ValidateItem(itemB).ContinueConditional(
(r => r.Success) => await this.ValidateItem(itemC).ContinueConditional(
(r => r.Success) => await this.ValidateItem(itemD))));
return result;
Basically, the return value of this.ValidateItem(itemA) is assigned to result and then this value goes into the ContinueConditional. If the result, r, has Success == true, then continue with validating itemB, and so on. If it's not successful, then exit out of that code and go straight to the return statement at the bottom, thus returning the item which failed the validation. If all items are validated then it will go to that return statement anyway.
I apologize for the long wall of text, especially if the c# construct already exists and is obvious. I appreciate any help with this.
Note: I have simplified my real example for the sake of posting this. One important part of the simplification is that itemA, itemB, itemC, and itemD are not the same type, despite referring to them as MyClass in the method signature above. So I can't just put them in a list and use LINQ. This also means I actually have different validation methods to accept the various items I have, but I didn't want to explain all that at the top in case it over-complicates things. The important part is that they all return ValidationMessage.
What about inverting the logic around the check for success/failure?
ValidationMessage result = await this.ValidateItem(itemA);
if (result.Success) result = await this.ValidateItem(itemB);
if (result.Success) result = await this.ValidateItem(itemC);
if (result.Success) result = await this.ValidateItem(itemD);
return result;
How about this? You can extend it to as many elements as you want.
var validationRules = new List<Func<Task<bool>>>(){
() => ValidateItem(itemA),
() => ValidateItem(itemB),
() => ValidateItem(itemC),
() => ValidateItem(itemD),
};
ValidationMessage result = new ValidationMessage();
foreach(var validationRule in validationRules)
{
result = await validationRule();
if(!result)
return result;
}
return result;
You could use FluentValidation to create a custom rule to loop through a list of models to validate. This is more overhead than the other answers but it's a good approach with clean readable code.
First define your shared model
public class YourSharedModel()
{
List<MyClass> Models = new List<MyClass>();
}
Define the validator:
public class SharedModelValidator : AbstractValidator<YourSharedModel>
{
public SharedModelValidator()
{
CustomRule(BeValid)
}
public bool BeValid(ValidationErrors<YourSharedModel> validationFailures, YourSharedModel sharedModel, ValidationContext<YourSharedModel> validationContext)
{
for (var m in sharedModel.Models)
{
var result = YourValidationMethod(m);
if (!result.Success)
{
validationFailures.AddFailureFor(x => m, result.ErrorMessage);
return false;
}
}
return true;
}
private YourValidationMethod(MyClass model)
{
// the code you have that actually validates
}
}
Implementation would be something like:
var yourSharedModel = new YourSharedModel();
yourSharedModel.Models.Add(itemA);
yourSharedModel.Models.Add(itemB);
// etc
var validator = new SharedModelValidator();
var results = validator.Validate(yourSharedModel);
if (!results.IsValid)
{
// do something with results.Errors
}
You could also keep all your errors and return false after the loop so that you get all the errors at once rather than one by one on submission, of course that's assuming a form post from a web application. Makes for a better user friendly experience.
You could use an interface and then add your items to a list that you foreach through:
ValidationMessage Test()
{
List<IValidatable> items = new List<IValidatable>();
MyClass1 item1 = new MyClass1();
MyClass2 item2 = new MyClass2();
items.Add(item1);
items.Add(item2);
ValidationMessage result = null;
foreach (var i in items)
{
result = i.ValidateItem();
if (!result.Success) break;
}
return result;
}
interface IValidatable
{
ValidationMessage ValidateItem();
}
public class ValidationMessage
{
public bool Success { get; set; }
public string ErrorMessage { get; set; }
}
public class MyClass1 : IValidatable
{
public ValidationMessage ValidateItem()
{
return new ValidationMessage();
}
}
public class MyClass2 : IValidatable
{
public ValidationMessage ValidateItem()
{
return new ValidationMessage();
}
}
Another (perhaps anorthodox) way to do it is to overload the && operator on ValidationMessage:
public class ValidationMessage
{
public bool Success { get; set; }
public string ErrorMessage { get; set; }
public static ValidationMessage operator &(ValidationMessage message1, ValidationMessage message2)
{
return message1.Success ? message2 : message1;
}
public static ValidationMessage operator |(ValidationMessage message1, ValidationMessage message2)
{
return message1.Success ? message1 : message2;
}
public static bool operator true(ValidationMessage message)
{
return message.Success;
}
public static bool operator false(ValidationMessage message)
{
return !message.Success;
}
}
Then you can do:
return (await this.ValidateItem(ItemA)) &&
(await this.ValidateItem(ItemB)) &&
(await this.ValidateItem(ItemC)) &&
(await this.ValidateItem(ItemD));
I have a couple validators that is validating an IDeliveryObject, which conceptually can be described as a file with several rows. That part is working fine.
IEnumerable<IDeliveryValidator> _validators; // Populated in ctor. Usually around 20 different validators.
private IEnumerable<IValidationResult> Validate(IDeliveryObject deliveryObject)
{
var validationErrors = new List<IValidationResult>();
int maxNumberOfErrors = 10;
foreach (IDeliveryValidator deliveryValidator in _validators)
{
IEnumerable<IValidationResult> results = deliveryValidator.Validate(deliveryObject).Take(maxNumberOfErrors);
validationErrors.AddRange(results);
if (validationErrors.Count >= maxNumberOfErrors )
{
return validationErrors.Take(maxNumberOfErrors).ToList();
}
}
return validationErrors;
}
The logic iterates through a couple of validators, which all validates the file for different things.
And a validator can look something like this:
public IEnumerable<IValidationResult> Validate(IDeliveryObject deliveryObject)
{
using (var reader = File.OpenText(deliveryObject.FilePath))
{
int expectedLength = 10; // Or some other value.
string line;
while ((line = reader.ReadLine()) != null)
{
var lineLength = line.Length;
if (lineLength != expectedLength)
{
// yield an error for each incorrect row.
yield return new DeliveryValidationResult("Wrong length...");
}
}
}
}
The ValidationResult looks like this:
public class DeliveryValidationResult : ValidationResult, IValidationResult
{
public DeliveryValidationResult(bool isSoftError, string errorMessage) : base(errorMessage)
{
IsSoftError = isSoftError;
}
public DeliveryValidationResult(string errorMessage) : base(errorMessage)
{
}
public DeliveryValidationResult(string errorMessage, IEnumerable<string> memberNames) : base(errorMessage, memberNames)
{
}
public DeliveryValidationResult(ValidationResult validationResult) : base(validationResult)
{
}
public bool IsSoftError { get; set; }
}
public interface IValidationResult
{
string ErrorMessage { get; set; }
bool IsSoftError { get; set; }
}
Thanks to Take(maxNumberOfErrors) and yield each validator will only return 10 validationresults, which used to be fine. But now I need to handle "soft validation result", which is the same kind of validation result, but it should not be included in the number of results yielded. It's a kind of warning, which is defined by setting IsSoftError in IValidationResult. A validator can yield both "soft validation result" and "regular validation result".
What I want is to take x validation results + unlimited soft validation results, so that all IValidationResults with IsSoftError == true will be included in the collection, but not in the count. I know that it sounds weird, but the concept is that there's no need to keep validating the file after x errors, but the validation can return unlimited "warnings".
It's very important that the Enumeration isn't enumerated more than one time, because it's CPU-heavy. Below is the code I want to change.
private IEnumerable<IValidationResult> Validate(IDeliveryObject deliveryObject)
{
var validationErrors = new List<IValidationResult>();
int maxNumberOfErrors = 10;
foreach (IDeliveryValidator deliveryValidator in _validators)
{
// Here I want results to contain MAX 10 regular validation results, but unlimited soft validation results
IEnumerable<IValidationResult> results = deliveryValidator.Validate(deliveryObject).Take(maxNumberOfErrors);
validationErrors.AddRange(results);
if (validationErrors.Count(x => !x.IsSoftError) >= maxNumberOfErrors)
{
return validationErrors.Take(maxNumberOfErrors).ToList();
}
}
return validationErrors;
}
EDIT:
When I got 10 'hard' errors I want to stop the cycle completely. The main issue here is that the cycle doesn't stop when 10 'soft' errors occured.
In case you want to completely stop after 10 'hard' errors, you could try this:
int count = 0;
IEnumerable<IValidationResult> results = deliveryValidator.Validate(deliveryObject)
.TakeWhile(error => error.IsSoftError || count++ < maxNumberOfErrors);
this would stop when the 11th hard error is encountered.
//Go through all the items and sort them into Soft and NotSoft
//But ultimately these are in memory constructs...so this is fast.
var foo = Validate(delivery).ToLookup(x => x.IsSoftError);
var soft = foo[true];
var hard = foo[false].Take(10);
var result = Enumerable.Concat(soft, hard);
This is really a generic (and probably a more subjective too) question. I have some classes where I use an interface to define a standard approach to validating the object state. When I did this, I got to scratching my head... is it best to
1.) allow the constructor (or initializing method) to silently filter out the errant information automatically or...
2.) allow the client to instantiate the object however and let the client also call the interface's IsValid property or Validate() method before moving forward?
Basically one approach is silent but could be misleading in that the client may not be aware that certain pieces of information were filtered away due to it not meeting the validation criteria. The other approach then would be more straight forward, but also adds a step or two? What's typical here?
Okay, after a long day of trying to keep up with some other things, I finally did come up with an example. Please for me for it as it's not ideal and by no means something wonderful, but hopefully should serve well enough to get the point across. My current project is just too complicated to put something simple out for this, so I made something up... and trust me... totally made up.
Alright, the objects in the example are this:
Client: representing client-side code (Console App btw)
IValidationInfo: This is the actual interface I'm using in my current project. It allows me to create a validation framework for the "back-end" objects not necessarily intended for the Client to use since the business logic could be complicated enough. This also allowed me to separate validation code and call as-needed for the business logic.
OrderManager: This is an object the client-side code can use to manage their orders. It's client-friendly so-to-speak.
OrderSpecification: This is an object the client-side code can use to request an order. But if the business logic doesn't work out, an exception can be raised (or if necessary the order not added and exceptions ignored...) In my real-world example I actually have an object that's not quite so black-and-white as to which side of this fence it goes... thus my original question when I realized I could push validation request (calling IsValid or Validate()) to the cilent.
CustomerDescription: represents customers to which I've classified (pretending to have been read from a DB.
Product: Represents a particular product which is classified also.
OrderDescription: Represents the official order request.The business rule is that the Customer cannot order anything to which they've not been classified (I know.. that's not very real-world, but it gave me something to work with...)
Ok... I just realized I can't attach a file here, so here's the code. I apologize for it's lengthy appearance. That was the best I could do to create a client-friendly front-end and business logic back-end using my Validation interface:
public class Client
{
static OrderManager orderMgr = new OrderManager();
static void Main(string[] args)
{
//Request a new order
//Note: Only the OrderManager and OrderSpecification are used by the Client as to keep the
// Client from having to know and understand the framework beyond that point.
OrderSpecification orderSpec = new OrderSpecification("Customer1", new Product(IndustryCategory.FoodServices, "Vending Items"));
orderMgr.SubmitOrderRequest(orderSpec);
Console.WriteLine("The OrderManager has {0} items for {1} customers.", orderMgr.ProductCount, orderMgr.CustomerCount);
//Now add a second item proving that the business logic to add for an existing customer works
Console.WriteLine("Adding another valid item for the same customer.");
orderSpec = new OrderSpecification("Customer1", new Product(IndustryCategory.FoodServices, "Sodas"));
orderMgr.SubmitOrderRequest(orderSpec);
Console.WriteLine("The OrderManager now has {0} items for {1} customers.", orderMgr.ProductCount, orderMgr.CustomerCount);
Console.WriteLine("Adding a new valid order for a new customer.");
orderSpec = new OrderSpecification("Customer2", new Product(IndustryCategory.Residential, "Magazines"));
orderMgr.SubmitOrderRequest(orderSpec);
Console.WriteLine("The OrderManager now has {0} items for {1} customers.", orderMgr.ProductCount, orderMgr.CustomerCount);
Console.WriteLine("Adding a invalid one will not work because the customer is not set up to receive these kinds of items. Should get an exception with message...");
try
{
orderSpec = new OrderSpecification("Customer3", new Product(IndustryCategory.Residential, "Magazines"));
orderMgr.SubmitOrderRequest(orderSpec);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
}
public interface IValidationInfo
{
string[] ValidationItems { get; }
bool IsValid { get; }
void Validate();
List<string> GetValidationErrors();
string GetValidationError(string itemName);
}
public class OrderManager
{
private List<OrderDescription> _orders = new List<OrderDescription>();
public List<OrderDescription> Orders
{
get { return new List<OrderDescription>(_orders); }
private set { _orders = value; }
}
public int ProductCount
{
get
{
int itemCount = 0;
this.Orders.ForEach(o => itemCount += o.Products.Count);
return itemCount;
}
}
public int CustomerCount
{
get
{
//since there's only one customer per order, just return the number of orders
return this.Orders.Count;
}
}
public void SubmitOrderRequest(OrderSpecification orderSpec)
{
if (orderSpec.IsValid)
{
List<OrderDescription> orders = this.Orders;
//Since the particular customer may already have an order, we might as well add to an existing
OrderDescription existingOrder = orders.FirstOrDefault(o => string.Compare(orderSpec.Order.Customer.Name, o.Customer.Name, true) == 0) as OrderDescription;
if (existingOrder != null)
{
List<Product> existingProducts = orderSpec.Order.Products;
orderSpec.Order.Products.ForEach(p => existingOrder.AddProduct(p));
}
else
{
orders.Add(orderSpec.Order);
}
this.Orders = orders;
}
else
orderSpec.Validate(); //Let the OrderSpecification pass the business logic validation down the chain
}
}
public enum IndustryCategory
{
Residential,
Textile,
FoodServices,
Something
}
public class OrderSpecification : IValidationInfo
{
public OrderDescription Order { get; private set; }
public OrderSpecification(string customerName, Product product)
{
//Should use a method in the class to search and retrieve Customer... pretending here
CustomerDescription customer = null;
switch (customerName)
{
case "Customer1":
customer = new CustomerDescription() { Name = customerName, Category = IndustryCategory.FoodServices };
break;
case "Customer2":
customer = new CustomerDescription() { Name = customerName, Category = IndustryCategory.Residential };
break;
case "Customer3":
customer = new CustomerDescription() { Name = customerName, Category = IndustryCategory.Textile };
break;
}
//Create an OrderDescription to potentially represent the order... valid or not since this is
//a specification being used to request the order
this.Order = new OrderDescription(new List<Product>() { product }, customer);
}
#region IValidationInfo Members
private readonly string[] _validationItems =
{
"OrderDescription"
};
public string[] ValidationItems
{
get { return _validationItems; }
}
public bool IsValid
{
get
{
List<string> validationErrors = GetValidationErrors();
if (validationErrors != null && validationErrors.Count > 0)
return false;
else
return true;
}
}
public void Validate()
{
List<string> errorMessages = GetValidationErrors();
if (errorMessages != null && errorMessages.Count > 0)
{
StringBuilder errorMessageReported = new StringBuilder();
errorMessages.ForEach(em => errorMessageReported.AppendLine(em));
throw new Exception(errorMessageReported.ToString());
}
}
public List<string> GetValidationErrors()
{
List<string> errorMessages = new List<string>();
foreach (string item in this.ValidationItems)
{
string errorMessage = GetValidationError(item);
if (!string.IsNullOrEmpty(errorMessage))
errorMessages.Add(errorMessage);
}
return errorMessages;
}
public string GetValidationError(string itemName)
{
switch (itemName)
{
case "OrderDescription":
return ValidateOrderDescription();
default:
return "Invalid item name.";
}
}
#endregion
private string ValidateOrderDescription()
{
string errorMessage = string.Empty;
if (this.Order == null)
errorMessage = "Order was not instantiated.";
else
{
if (!this.Order.IsValid)
{
List<string> orderErrors = this.Order.GetValidationErrors();
orderErrors.ForEach(ce => errorMessage += "\n" + ce);
}
}
return errorMessage;
}
}
public class CustomerDescription : IValidationInfo
{
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public int ZipCode { get; set; }
public IndustryCategory Category { get; set; }
#region IValidationInfo Members
private readonly string[] _validationItems =
{
"Name",
"Street",
"City",
"State",
"ZipCode",
"Category"
};
public string[] ValidationItems
{
get { return _validationItems; }
}
public bool IsValid
{
get
{
List<string> validationErrors = GetValidationErrors();
if (validationErrors != null && validationErrors.Count > 0)
return false;
else
return true;
}
}
public void Validate()
{
List<string> errorMessages = GetValidationErrors();
if (errorMessages != null && errorMessages.Count > 0)
{
StringBuilder errorMessageReported = new StringBuilder();
errorMessages.ForEach(em => errorMessageReported.AppendLine(em));
throw new Exception(errorMessageReported.ToString());
}
}
public List<string> GetValidationErrors()
{
List<string> errorMessages = new List<string>();
foreach (string item in this.ValidationItems)
{
string errorMessage = GetValidationError(item);
if (!string.IsNullOrEmpty(errorMessage))
errorMessages.Add(errorMessage);
}
return errorMessages;
}
public string GetValidationError(string itemName)
{
//Validation methods should be called here... pretending nothings wrong for sake of discussion & simplicity
switch (itemName)
{
case "Name":
return string.Empty;
case "Street":
return string.Empty;
case "City":
return string.Empty;
case "State":
return string.Empty;
case "ZipCode":
return string.Empty;
case "Category":
return string.Empty;
default:
return "Invalid item name.";
}
}
#endregion
}
public class Product
{
public IndustryCategory Category { get; private set; }
public string Description { get; private set; }
public Product(IndustryCategory category, string description)
{
this.Category = category;
this.Description = description;
}
}
public class OrderDescription : IValidationInfo
{
public CustomerDescription Customer { get; private set; }
private List<Product> _products = new List<Product>();
public List<Product> Products
{
get { return new List<Product>(_products); }
private set { _products = value; }
}
public OrderDescription(List<Product> products, CustomerDescription customer)
{
this.Products = products;
this.Customer = customer;
}
public void PlaceOrder()
{
//If order valid, place
if (this.IsValid)
{
//Do stuff to place order
}
else
Validate(); //cause the exceptions to be raised with the validate because business rules were broken
}
public void AddProduct(Product product)
{
List<Product> productsToEvaluate = this.Products;
//some special read, validation, quantity check, pre-existing, etc here
// doing other stuff...
productsToEvaluate.Add(product);
this.Products = productsToEvaluate;
}
#region IValidationInfo Members
private readonly string[] _validationItems =
{
"Customer",
"Products"
};
public string[] ValidationItems
{
get { return _validationItems; }
}
public bool IsValid
{
get
{
List<string> validationErrors = GetValidationErrors();
if (validationErrors != null && validationErrors.Count > 0)
return false;
else
return true;
}
}
public void Validate()
{
List<string> errorMessages = GetValidationErrors();
if (errorMessages != null && errorMessages.Count > 0)
{
StringBuilder errorMessageReported = new StringBuilder();
errorMessages.ForEach(em => errorMessageReported.AppendLine(em));
throw new Exception(errorMessageReported.ToString());
}
}
public List<string> GetValidationErrors()
{
List<string> errorMessages = new List<string>();
foreach (string item in this.ValidationItems)
{
string errorMessage = GetValidationError(item);
if (!string.IsNullOrEmpty(errorMessage))
errorMessages.Add(errorMessage);
}
return errorMessages;
}
public string GetValidationError(string itemName)
{
switch (itemName)
{
case "Customer":
return ValidateCustomer();
case "Products":
return ValidateProducts();
default:
return "Invalid item name.";
}
}
#endregion
#region Validation Methods
private string ValidateCustomer()
{
string errorMessage = string.Empty;
if (this.Customer == null)
errorMessage = "CustomerDescription is missing a valid value.";
else
{
if (!this.Customer.IsValid)
{
List<string> customerErrors = this.Customer.GetValidationErrors();
customerErrors.ForEach(ce => errorMessage += "\n" + ce);
}
}
return errorMessage;
}
private string ValidateProducts()
{
string errorMessage = string.Empty;
if (this.Products == null || this.Products.Count <= 0)
errorMessage = "Invalid Order. Missing Products.";
else
{
foreach (Product product in this.Products)
{
if (product.Category != Customer.Category)
{
errorMessage += string.Format("\nThe Product, {0}, category does not match the required Customer category for {1}", product.Description, Customer.Name);
}
}
}
return errorMessage;
}
#endregion
}
Any reason you wouldn't want the constructor to noisily throw an exception if the information is valid? It's best to avoid ever creating an object in an invalid state, in my experience.
It's completely depends on the client. There's a trade-off as you already mentioned. By default approach number 1 is my favorite. Creating smart classes with good encapsulation and hiding details from client. The level of smartness depends who is going to use the object. If client is business aware you can reveal details according to the level of this awareness. This is a dichotomy and should not be treated as black or white.
Well if I correctly understood, there are basically two question - whether you should fail right away or later and whether you should omit/assume certain information.
1) I always prefer failing as soon as possible - good example is failing at compile time vs failing at run time - you always want to fail at compile time. So if something is wrong with the state of some object, as Jon said - throw exception right away as loudly as you can and deal with it - do not introduce additional complexity down the road as you'll be heading for if/elseif/elseif/elseif/else mumbo jumbo.
2) When it comes to user input, if you are in position to simply filter out errors automatically - just do it. For example, I almost never ask users for country - if I really need it, I automatically detect it from IP and display it in the form. It's way easier if user just needs to confirm/change the data - and I don't need to deal with null situation.
Now, in case we are talking about the data generated by code during some processing - for me situation is drastically different - I always want to know an much as possible (for easier debugging down the road) and ideally you never should destroy any piece of information.
To wrap up, in your case I would recommend that you keep IsValid as simple yes/no (not yes/no/maybe/kindaok/etc). If you can fix some problems automatically - do it, but consider that they keep object in IsValid yes. For everything else, you throw exception and go to IsValid=no.
I'm maintaining a legacy WebForms application and one of the pages just serves GET requests and works with many query string parameters. This work is done in the code-behind and does a lot of this type of check and casting.
protected override void OnLoad(EventArgs e)
{
string error = string.Empty;
string stringParam = Request.Params["stringParam"];
if (!String.IsNullOrEmpty(stringParam))
{
error = "No parameter";
goto LoadError;
}
Guid? someId = null;
try
{
someId = new Guid(Request.Params["guidParam"]);
}
catch (Exception){}
if (!someId.HasValue)
{
error = "No valid id";
goto LoadError;
}
// parameter checks continue on
LoadError:
log.ErrorFormat("Error loading page: {0}", error);
// display error page
}
I'd like to create a testable class that encapsulates this parsing and validation and moves it out of the code-behind. Can anyone recommend some approaches to this and/or examples?
As a first big step, I'd probably create some form of mapper/translator object, like this:
class SpecificPageRequestMapper
{
public SpecificPageRequest Map(NameValueCollection parameters)
{
var request = new SpecificPageRequest();
string stringParam = parameters["stringParam"];
if (String.IsNullOrEmpty(stringParam))
{
throw new SpecificPageRequestMappingException("No parameter");
}
request.StringParam = stringParam;
// more parameters
...
return request;
}
}
class SpecificPageRequest
{
public string StringParam { get; set; }
// more parameters...
}
Then your OnLoad could look like this:
protected override void OnLoad(EventArgs e)
{
try
{
var requestObject = requestMapper.Map(Request.Params);
stringParam = requestObject.StringParam;
// so on, so forth. Unpack them to the class variables first.
// Eventually, just use the request object everywhere, maybe.
}
catch(SpecificPageRequestMappingException ex)
{
log.ErrorFormat("Error loading page: {0}", ex.Message);
// display error page
}
}
I've omitted the code for the specific exception I created, and assumed you instantiate a mapper somewhere in the page behind.
Testing this new object should be trivial; you set the parameter on the collection passed into Map, then assert that the correct parameter on the request object has the value you expect. You can even test the log messages by checking that it throws exceptions in the right cases.
Assuming that you may have many such pages using such parameter parsing, first create a simple static class having extension methods on NamedValueCollection. For example,
static class Parser
{
public static int? ParseInt(this NamedValueCollection params, string name)
{
var textVal = params[name];
int result = 0;
if (string.IsNullOrEmpty(textVal) || !int.TryParse(textVal, out result))
{
return null;
}
return result;
}
public static bool TryParseInt(this NamedValueCollection params, string name, out int result)
{
result = 0;
var textVal = params[name];
if (string.IsNullOrEmpty(textVal))
return false;
return int.TryParse(textVal, out result);
}
// ...
}
Use it as follows
int someId = -1;
if (!Request.Params.TryParseInt("SomeId", out someId))
{
// error
}
Next step would be writing page specific parser class. For example,
public class MyPageParser
{
public int? SomeId { get; private set; }
/// ...
public IEnumerable<string> Parse(NamedValueCollection params)
{
var errors = new List<string>();
int someId = -1;
if (!params.TryParseInt("SomeId", out someId))
{
errors.Add("Some id not present");
this.SomeId = null;
}
this.SomeId = someId;
// ...
}
}