Refactor my code: Conditions based on different variables
Given:
internal void Configure(ButtonEventArgs args, IBroker broker, FunctionEntry entry)
{
int phase = broker.TradingPhase;
if (args.Button == ItemType.SendAutoButton)
{
if (phase == 1)
{
entry.SetParameter("ANDealerPrice", -1);
entry.SetParameter("ANAutoUpdate", 4);
}
else if (phase == 2)
{
entry.SetParameter("ANDealerPrice", -1);
entry.SetParameter("ANAutoUpdate", 2);
}
}
if (phase == 1)
{
if (broker.IsCashBMK)
{
entry.SetParameter("Value", 100);
}
else if (broker.IsCross)
{
entry.SetParameter("Value", 200);
}
}
}
I am looking for suggestions to refactor the code above. As suggested by Fowler: "Replace condition with Strategy/polymorphism", i fail to implement an effective code on these line. Since there are multiple conditions, base on mutiple variables.
Please suggest if there could be a pattern that could eliminate these error-prone and ugly conditions (code smell).
Thanks for your interest.
Edit: 1) My intention is to apply Open-Closed principle here, so that if tommorrow if there i开发者_运维问答s a change in logic i can extend these conditions by introducing a new class. 2) Please don't mind the magic numbers, in real scenario i have valid constant/source for them.
For what you've presented so far, I would be inclined to separate out the three parameters, each into its own function, thus:
void SetAnDealerPrice(ButtonEventArgs args, IBroker broker,
FunctionEntry entry) {
if (args.Button != ItemType.SendAutoButton)
return;
int phase = broker.TradingPhase;
if (phase == 1 || phase == 2)
entry.SetParameter("ANDealerPrice", -1);
}
void SetAnAutoUpdate(ButtonEventArgs args, IBroker broker,
FunctionEntry entry) {
if (args.Button != ItemType.SendAutoButton)
return;
switch (broker.TradingPhase) {
case 1:
entry.SetParameter("ANAutoUpdate", 4);
break;
case 2:
entry.SetParameter("ANAutoUpdate", 2);
break;
}
}
void SetValue(IBroker broker, FunctionEntry entry) {
if (broker.TradingPhase != 1)
return;
entry.SetParameter("Value", broker.IsCashBMK ? 100 : 200);
}
This is somewhat hand-crafted (doesn't lend itself well to semi-automated updating as rules change), and marginally less efficient (some conditions are being checked, and some fields being referenced, multiple times, plus of course it's more function calls). I don't think efficiency matters until you have a demonstrated problem (and when you do, bigger changes than these will be required), and this approach leaves you knowing exactly what code to look at when the rules for a given parameter change. I don't believe polymorphism is likely to lead you to a good solution here.
In order for a strategy to be applied, it needs to be in the object which has the data which chooses the strategy. You're pulling data from both the broker, the broker's trading phase, and a button.
To do that combination with polymorphism requires triple dispatch, and would be far more complicated than the current code.
You might want to separate by phase, and apply Demeter.
There's also a lot of magic numbers. If you are translating from one system to another, then make them constants of the system being translated to, otherwise move them into your data model.
It's hard to say without knowing more about your code but the problems looks like you're trying to do more than one thing in the method, also, have code dependent on the UI in a data method like that looks pretty ugly.
It seems like you should have at least two methods, one GetBrokerPrice and one SetPriceOptions
Two obvious refactorings come to mind:
- Remove the
else
: now the ifs can be rearranged more easily. - Reorder the if's so that
if (phase == ...)
is always the outer one.
If you want, you can reorder the if-blocks to combine the if (phase == 1)
blocks, although I think I would repeat that if (phase == 1)
multiple times to prepare for the next step.
Those refactorings make it easier to apply the one below.
internal void Configure(ButtonEventArgs args, IBroker broker, FunctionEntry entry)
{
int phase = broker.TradingPhase;
if (phase == 1)
{
if (args.Button == ItemType.SendAutoButton)
{
entry.SetParameter("ANDealerPrice", -1);
entry.SetParameter("ANAutoUpdate", 4);
}
}
if (phase == 2)
{
if (args.Button == ItemType.SendAutoButton)
{
entry.SetParameter("ANDealerPrice", -1);
entry.SetParameter("ANAutoUpdate", 2);
}
}
if (phase == 1)
{
if (broker.IsCashBMK)
{
entry.SetParameter("Value", 100);
}
}
if (phase == 1)
{
if (broker.IsCross)
{
entry.SetParameter("Value", 200);
}
}
}
Now you have a long list of small if-blocks. This can be refactored into a List<MyAction>
. Somewhere you have to populate this list, but traversing it is pretty straightforward:
internal void Configure(ButtonEventArgs args, IBroker broker, FunctionEntry entry)
{
foreach(var action in MyActions)
{
action(args, broker, entry);
}
}
internal void PopulateMyActions()
{
// Hopefully I have not made a syntax error in this code...
MyActions.Add( (ButtonEventArgs args, IBroker broker, FunctionEntry entry) =>
{
if (broker.TradingPhase == 1)
{
if (args.Button == ItemType.SendAutoButton)
{
entry.SetParameter("ANDealerPrice", -1);
entry.SetParameter("ANAutoUpdate", 4);
}
}
} );
// And so on
}
An alternative is to create separate lists for phase == 1 and phase == 2, and leave out the broker from the call to action
:
internal void Configure(ButtonEventArgs args, IBroker broker, FunctionEntry entry)
{
int phase = broker.TradingPhase;
foreach(var action in MyActions[phase])
{
action(args, entry);
}
}
internal void PopulateMyActions()
{
// Hopefully I have not made a syntax error in this code...
MyActions[1].Add( (ButtonEventArgs args, FunctionEntry entry) =>
{
if (args.Button == ItemType.SendAutoButton)
{
entry.SetParameter("ANDealerPrice", -1);
entry.SetParameter("ANAutoUpdate", 4);
}
} );
// And so on
}
I think I prefer the latter, as it make the special role of phase
more explicit.
Additional refactorings could be to replace action(args, entry)
with action(args.Button, entry)
, but I can't judge whether that is appropiate.
In the future, populating the list could be done dynamically, e.g. when loading an assembly. Which assembly to load could then be controlled by a configuration setting. Presto: Switch behaviour without recompiling your core code!
PS: Code is untested as I'm away from a compiler at the moment. Feel free to edit my answer to remove syntax errors, to add the declaration of MyActions
, and so on.
It looks to me like all that function is doing is setting the string parameters of FunctionEntry
.
I would say FunctionEntry
should handle that logic.
That shouldn't be done by Configure()
.
I think it should pass the ItemType
of the ButtonEventArgs
and the IBrkoer
to the instance of FunctionEntry
and let it decide this if-else logic.
As far as the nested if-else's, I'm not too concerned about that.
Business logic can be as complex as it wants, especially when it lacks uniformity.
It would be more complex to make a look-up table for this logic, since you need a 3-dimensional table in that case: which button, which broker trading phase, and then which parameter to change. That would be a hassle to follow, to be honest.
精彩评论