将许多代码块编译成一个方法
Compiling many chunks of code into a single method
我有一个遗留方法可以实时处理各种数量。有很多数据,这种方法基本上是一大堆 if
/switch
决定如何根据某些规则计算目标值,并对从每个设备接收到的每个样本执行此操作(和有很多)。它的签名是这样的:
double Process(ITimestampedData data, IProcessingRule rule);
其中 ISample
包含单个时间戳的多个不同数量的值,而 IProcessingRule
定义要使用的值以及如何处理它以获得结果(然后可以将其与阈值)。
我想摆脱所有 if
s 和 switch
es 并将其重构为一个工厂,它将为每个规则创建一个单一的处理方法,然后 运行这些方法用于输入数据。由于这些规则有各种参数,我还想看看是否有一种方法可以在编译时完全解析所有这些分支(好吧,运行-时间,但我指的是我调用工厂方法一次 "compile" 我的处理委托)。
所以,我有类似的东西,但要复杂得多(更多相互依赖的条件和各种规则):
// this runs on each call
double result;
switch (rule.Quantity)
{
case QuantityType.Voltage:
{
Vector v;
if (rule.Type == VectorType.SinglePhase)
{
v = data.Vectors[Quantity.Voltage].Phases[rule.Phase];
if (rule.Phase == PhaseType.Neutral)
{
v = v * 2; // making this up just to make a point
}
}
else if (rule.Type == VectorType.Symmetry)
{
v = CalculateSymmetry(data.Vectors);
}
if (rule.TargetProperty == PropertyType.Magnitude)
{
result = v.Magnitude();
if (rule.Normalize)
{
result /= rule.NominalValue;
}
}
}
// ... this doesn't end so soon
变成这样:
// this is a factory method which will return a single delegate
// for each rule - and do it only once, at startup
Func<ITimestampedData, double> GetProcessor(IProcessingRule)
{
Func<ITimestampedData, Vectors> quantityGetter;
Func<Vectors, Vector> vectorGetter;
Func<Vector, double> valueGetter;
quantityGetter = data => data.Vectors[rule.Quantity];
if (rule.Type == VectorType.SinglePhase)
{
if (rule.Phase == PhaseType.Neutral)
vectorGetter = vectors => 2 * vectors.Phases[rule.Phase];
else
vectorGetter = vectors => vectors.Phases[rule.Phase];
}
else if (rule.Type == VectorType.Symmetry)
{
vectorGetter = vectors => CalculateSymmetry(vectors);
}
if (rule.TargetProperty == PropertyType.Magnitude)
{
if (rule.Normalize)
valueGetter = v => v.Magnitude() / rule.NominalValue;
else
valueGetter = v => v.Magnitude();
}
...
// now we just chain all delegates into a single "if-less" call
return data => valueGetter(vectorGetter(quantityGetter(data)));
}
但问题是:
- 我的方法中仍然有很多重复,
- 我已将
if
s 切换为多个委托调用,但性能没有任何改善,
- 虽然这个 "chain" 在工厂方法的末尾是固定的和已知的,但我仍然没有一个可以处理我的输入的编译方法。
所以,最后,我的问题是:
有没有办法以某种方式 "build" 从我工厂中的这些不同代码块中得到最终编译的方法?
我知道我可以使用类似 CSharpCodeProvider
的东西,创建一个巨大的字符串然后编译它,但我希望有更好的编译时支持和类型检查的东西。
工厂
switch 语句通常是代码中的难闻气味,您对它的感觉是完全正确的。但是工厂是 switch 语句的完全有效的地方。只是不要忘记工厂的职责是构造对象,因此请确保任何额外的逻辑都在工厂之外。另外,不要将工厂与工厂方法混淆。当您有一组多态可交换的 classes 并且您的工厂决定使用哪一个时,将使用第一个。此外,它有助于打破依赖关系。同时,工厂方法更像是静态构造函数,知道构造对象的所有依赖关系。我建议要小心工厂方法并更喜欢适当的 Factory classes。从 SRP 的角度考虑这一点 - 工厂的责任是构建对象,而您的 class 有一些业务责任。虽然您使用工厂方法,但您的 class 有两个职责。
缩进
我尝试遵循一个很好的规则,叫做 "One indentation level per method"。这意味着,您只能有更多级别的缩进,不包括根缩进。那是一段有效且可读的代码:
function something() {
doSomething();
if (isSomethingValid()) {
doSomethingElse();
}
return someResult();
}
尝试遵循这个规则,通过提取私有方法,你会发现代码变得更加清晰。
If/Else 语句
事实证明,else 语句始终是可选的,您始终可以重构代码以不使用它。解决方案很简单——尽早使用 returns。您的方法将变得更短并且更具可读性。
可能我的回答不足以解决你所有的问题,但至少给了你一些思考的思路。
如果您使用遗留代码,我强烈建议您阅读 Michael Feathers 的 "Working Effectively with Legacy Code" 书,当然还有 Martin Fowler 的 "Refactoring" 书。
考虑让您的规则在其中包含更多功能。
你知道你想要什么规则,因为你传入了它。但是在你当前的代码中,你询问 运行 本身来确定你进行的计算。建议你把规则做的更智能一点,结果问规则。
例如,执行最多计算的规则是 SinglePhaseNeutralVoltageMagnitudeNormalizedRule。
class SinglePhaseNeutralVoltageMagnitudeNormalizedRule implements IProcessingRule
{
double calculate(ITimestampedData data)
{
double result;
Vector v;
v = data.Vectors[Quantity.Voltage].Phases[Phase];
v = v * 2; // making this up just to make a point
result = v.Magnitude();
result /= NominalValue;
return result;
}
}
所以Process方法变得简单多了
result = rule.calculate(data);
@SergeKuharev 建议的工厂 class 如果那里有很多复杂性,可以使用它来构建规则。另外,如果规则本身之间有很多共同的代码可以重构到一个共同的地方。
例如,规范化可以是简单地包装另一个规则的规则。
class NormalizeRule IProcessingRule
{
private IProcessingRule priorRule;
private double nominalValue;
public NormalizeRule(IProcessingRule priorRule, double nominalValue)
{
priorRule = priorRule;
nominalValue = nominalValue;
}
public double calculate(ITimestampedData data)
{
return priorRule.calculate(data)/nominalValue;
}
}
因此,鉴于此和 class SinglePhaseNeutralVoltageMagnitudeRule(如上所述减去 /= nominalValue),工厂可以将两者结合起来,通过组合来制作 SinglePhaseNeutralVoltageMagnitudeNrmalizedRule。
我有一个遗留方法可以实时处理各种数量。有很多数据,这种方法基本上是一大堆 if
/switch
决定如何根据某些规则计算目标值,并对从每个设备接收到的每个样本执行此操作(和有很多)。它的签名是这样的:
double Process(ITimestampedData data, IProcessingRule rule);
其中 ISample
包含单个时间戳的多个不同数量的值,而 IProcessingRule
定义要使用的值以及如何处理它以获得结果(然后可以将其与阈值)。
我想摆脱所有 if
s 和 switch
es 并将其重构为一个工厂,它将为每个规则创建一个单一的处理方法,然后 运行这些方法用于输入数据。由于这些规则有各种参数,我还想看看是否有一种方法可以在编译时完全解析所有这些分支(好吧,运行-时间,但我指的是我调用工厂方法一次 "compile" 我的处理委托)。
所以,我有类似的东西,但要复杂得多(更多相互依赖的条件和各种规则):
// this runs on each call
double result;
switch (rule.Quantity)
{
case QuantityType.Voltage:
{
Vector v;
if (rule.Type == VectorType.SinglePhase)
{
v = data.Vectors[Quantity.Voltage].Phases[rule.Phase];
if (rule.Phase == PhaseType.Neutral)
{
v = v * 2; // making this up just to make a point
}
}
else if (rule.Type == VectorType.Symmetry)
{
v = CalculateSymmetry(data.Vectors);
}
if (rule.TargetProperty == PropertyType.Magnitude)
{
result = v.Magnitude();
if (rule.Normalize)
{
result /= rule.NominalValue;
}
}
}
// ... this doesn't end so soon
变成这样:
// this is a factory method which will return a single delegate
// for each rule - and do it only once, at startup
Func<ITimestampedData, double> GetProcessor(IProcessingRule)
{
Func<ITimestampedData, Vectors> quantityGetter;
Func<Vectors, Vector> vectorGetter;
Func<Vector, double> valueGetter;
quantityGetter = data => data.Vectors[rule.Quantity];
if (rule.Type == VectorType.SinglePhase)
{
if (rule.Phase == PhaseType.Neutral)
vectorGetter = vectors => 2 * vectors.Phases[rule.Phase];
else
vectorGetter = vectors => vectors.Phases[rule.Phase];
}
else if (rule.Type == VectorType.Symmetry)
{
vectorGetter = vectors => CalculateSymmetry(vectors);
}
if (rule.TargetProperty == PropertyType.Magnitude)
{
if (rule.Normalize)
valueGetter = v => v.Magnitude() / rule.NominalValue;
else
valueGetter = v => v.Magnitude();
}
...
// now we just chain all delegates into a single "if-less" call
return data => valueGetter(vectorGetter(quantityGetter(data)));
}
但问题是:
- 我的方法中仍然有很多重复,
- 我已将
if
s 切换为多个委托调用,但性能没有任何改善, - 虽然这个 "chain" 在工厂方法的末尾是固定的和已知的,但我仍然没有一个可以处理我的输入的编译方法。
所以,最后,我的问题是:
有没有办法以某种方式 "build" 从我工厂中的这些不同代码块中得到最终编译的方法?
我知道我可以使用类似 CSharpCodeProvider
的东西,创建一个巨大的字符串然后编译它,但我希望有更好的编译时支持和类型检查的东西。
工厂
switch 语句通常是代码中的难闻气味,您对它的感觉是完全正确的。但是工厂是 switch 语句的完全有效的地方。只是不要忘记工厂的职责是构造对象,因此请确保任何额外的逻辑都在工厂之外。另外,不要将工厂与工厂方法混淆。当您有一组多态可交换的 classes 并且您的工厂决定使用哪一个时,将使用第一个。此外,它有助于打破依赖关系。同时,工厂方法更像是静态构造函数,知道构造对象的所有依赖关系。我建议要小心工厂方法并更喜欢适当的 Factory classes。从 SRP 的角度考虑这一点 - 工厂的责任是构建对象,而您的 class 有一些业务责任。虽然您使用工厂方法,但您的 class 有两个职责。
缩进
我尝试遵循一个很好的规则,叫做 "One indentation level per method"。这意味着,您只能有更多级别的缩进,不包括根缩进。那是一段有效且可读的代码:
function something() {
doSomething();
if (isSomethingValid()) {
doSomethingElse();
}
return someResult();
}
尝试遵循这个规则,通过提取私有方法,你会发现代码变得更加清晰。
If/Else 语句
事实证明,else 语句始终是可选的,您始终可以重构代码以不使用它。解决方案很简单——尽早使用 returns。您的方法将变得更短并且更具可读性。
可能我的回答不足以解决你所有的问题,但至少给了你一些思考的思路。
如果您使用遗留代码,我强烈建议您阅读 Michael Feathers 的 "Working Effectively with Legacy Code" 书,当然还有 Martin Fowler 的 "Refactoring" 书。
考虑让您的规则在其中包含更多功能。 你知道你想要什么规则,因为你传入了它。但是在你当前的代码中,你询问 运行 本身来确定你进行的计算。建议你把规则做的更智能一点,结果问规则。
例如,执行最多计算的规则是 SinglePhaseNeutralVoltageMagnitudeNormalizedRule。
class SinglePhaseNeutralVoltageMagnitudeNormalizedRule implements IProcessingRule
{
double calculate(ITimestampedData data)
{
double result;
Vector v;
v = data.Vectors[Quantity.Voltage].Phases[Phase];
v = v * 2; // making this up just to make a point
result = v.Magnitude();
result /= NominalValue;
return result;
}
}
所以Process方法变得简单多了
result = rule.calculate(data);
@SergeKuharev 建议的工厂 class 如果那里有很多复杂性,可以使用它来构建规则。另外,如果规则本身之间有很多共同的代码可以重构到一个共同的地方。
例如,规范化可以是简单地包装另一个规则的规则。
class NormalizeRule IProcessingRule
{
private IProcessingRule priorRule;
private double nominalValue;
public NormalizeRule(IProcessingRule priorRule, double nominalValue)
{
priorRule = priorRule;
nominalValue = nominalValue;
}
public double calculate(ITimestampedData data)
{
return priorRule.calculate(data)/nominalValue;
}
}
因此,鉴于此和 class SinglePhaseNeutralVoltageMagnitudeRule(如上所述减去 /= nominalValue),工厂可以将两者结合起来,通过组合来制作 SinglePhaseNeutralVoltageMagnitudeNrmalizedRule。