I am writing an application that performs physics simulations based on user input data. Although data is validated before beginning simulation, some issues might reveal themselves during simulation, and can be categorized into 3 types:
- Critical errors that stop the simulation;
- Ambiguous situations that pause the simulation and require user clarification on how to continue;
- Non-pausing warnings that are written into a stack and displayed after simulation finishes.
These issues might occur in any part of the simulation core code, at any call stack depth level. Core code must be completely separate from UI concerns.
Case #1 is easiest to handle - exception is raised anywhere in the core code, and is caught by the UI code that displays exception message.
Case #3 should ideally be something similar, but unlike exception, it should allow code to continue running, only writing warning messages to a stack that is "caught" (retrieved) by the caller (UI command) and parsed as required. This would be very easy to implement with dependency injection (inject instance of warning class to simulation core code), but it would require injecting it everywhere where a warning might potentially occur, littering signatures of almost all class constructors and function calls. Is there any other way to achieve this? If I make warning class static, then I won't have an instance to reference to retrieve the warnings. In any case, my goal is to make this warning code as un-intrusive as possible - without modifying signatures of class constructors or function calls.
Case #2 I have no idea how to handle. Basically the simulation code should pause when certain condition is met (ambiguous situation), return flow back to the UI with a warning message and certain choices for the user, and then resume running main code with this new information. I don't know how to implement this without mixing concerns. Any ideas?
Here is a mock code that illustrates what I'm trying to do. Note that user input data is omitted here, since it is no relevant:
public class Ui
{
public void RunSimulationCommand()
{
SimulationCore sim = new();
WarningsHandler warningsHandler = new();
try
{
sim.RunSimulation();
foreach (string warning in warningsHandler.Warnings) //For case #3
{
MessageBox.Show(warning);
}
}
catch (Exception e)
{
MessageBox.Show(e.Message); //For case #1
}
}
public bool ShowAlternativeInquiryMessage() //for case #2
{
MessageBoxResult result = MessageBox.Show("D was zero. Click Yes if this is expected, No to try alternative calculation", "", MessageBoxButton.YesNo);
return result == MessageBoxResult.No;
}
}
public class SimulationCore
{
public void RunSimulation()
{
PhysicsSimulationRunner.RunPhysics();
}
public static class PhysicsSimulationRunner
{
public static void RunPhysics()
{
int a = 0;
int b = 1 / a; //Case #1 - division by zero, will throw an exception caught by UI code
int c = GetValueC();
if (c > 1000) { warningsHandler.Add("C value was above 1000"); } //Case #3 - should add warning to the stack and continue running the code
int d = GetValueD();
if (d == 0)
{
bool tryAlternative = //Case #2 - should somehow call ShowAlternativeInquiryMessage and get user input
if (tryAlternative) { d = GetValueDAlt(); }
}
}
public static int GetValueC()
{
return 1001;
}
public static int GetValueD()
{
return 0;
}
public static int GetValueDAlt()
{
return 5;
}
}
}
public class WarningsHandler
{
public List<string> Warnings { get; init; } = new();
public void Add(string warning)
{
Warnings.Add(warning);
}
}
Please note that there is an intentional error in Case #3 - warningsHandler doesn't exist in that context, I'm only showing how I'd preferably call it - but I don't want to use dependency injection, because like I said it would clutter all the constructor/function signatures to get that reference to where it's needed.
Can anyone suggest how to solve cases #2 and #3?