我有一个WCF服务,有很多(~30)这样的方法:
public Foo GetFooById(string id)
{
try
{
return FooLogic.GetById(id);
}
catch (Exception ex)
{
throw LogAndThrowFaultException(ex);
}
}
除了try块中的一行之外,这些方法中的代码完全相同。这很简单,我甚至已经抽象记录并抛出异常。
本着DRY的精神,我可以更进一步做到这一点:
public Foo GetFooById(string id)
{
return PerformServiceOperation<Foo>(() => FooLogic.GetById(id));
}
为此,此方法将处理重复的try / catch代码并调用每个func:
private T PerformServiceOperation<T>(Func<T> func)
{
try
{
return func.Invoke();
}
catch (Exception ex)
{
throw LogAndThrowFaultException(ex);
}
}
这太过分了吗?代码是否已经变得简单,是否应该单独存放?或者将func传递给helper方法并让该方法处理重复的try / catch是一个好主意?我也关注可读性。我认为对辅助方法的调用有点难看。
答案 0 :(得分:4)
我不得不说,一般来说,在生产代码中,我倾向于认为DRY应该走极端。我有几个原因,我已经发展了几十年。
1)一个地方的“修复”得到传播。像你建议的那样一个简单的修复可以节省数小时的QA弹跳你的代码,因为一个接一个的方法失败了一个简单的测试。这可能是一个极端的情况,但我已经看到它发生了。虽然你知道这个代码是复制的,你必须在多个地方修复,下一个工程师是否知道这个?
顺便说一句,一个可行的替代方案是记录每个地方代码是否重复以引用其他位置,以便维护者知道去哪里。
2)程序员能力。通过坚持绝对0代码重复,您将学习将使您成为更好的工程师的技术。我看到有很多代码,工程师坚持认为他们不能让它变得更干 - 但我已经做了十几次(也许就像你在这里使用的那样),因为我d不得不在某个时候解开它。
3)可能是最不明显但最重要的 - 迭代重构。重构通常只能在层中完成。你肯定看过一堆凌乱的,无法解释的代码,只能看到最轻微的重构 - 然而在重构之后,其他人变得清晰,而那些使其他人成为可能。我采取了大量的方法,每个人都害怕触摸,并以这种方式将它们降低到原始尺寸的1/10。
4)数据提取。如果你坚持使用DRY,你最终会得到大量的数据,因为最基本的重构之一就是从类似的代码中提取不同的数据然后合并代码。这使得您的代码更易于维护 - 过去我经常能够解决在重构之前更改字符串数组的问题,通常需要数小时或数天。
我必须在任何地方让人们相信这一点(你可以通过查看对这个问题说“不”的人数来看到这一点)。 Extreme DRY在开始时通常是一个艰难的卖点 - 直到他们看到结果。我认为你个人最重要的事情就是承诺像你的才能让你一样干(你练习的时候会更多)。
如果您列出的特定重构最终会导致问题,那么,您已经学到了一些东西,并且下次可以采用不同的方法。你学会了,时间没有浪费。
顺便说一句,您将了解以其他人可以理解和使用的方式编写极其干燥的代码至关重要 - 这并不总是很容易,并且可能会导致某些人讨厌“重构”和“过度工程“直到他们发现他们正在处理的代码的人在沟通方面不够好(通过代码)。
PS:我应该提到另一个 - 因为编码DRY很有趣。复制和粘贴代码(并修复代码)是您作为程序员可以做的最悲惨的工作,但创建仍然可用和可维护的简短DRY代码是一种心理挑战,任何值得他的盐的工程师都应该享受这个难题。答案 1 :(得分:2)
从概念上讲,我并不觉得这太过分了。改变你只处理一次异常的方式,你会很高兴你决定不要一遍又一遍地输入相同的try-catch块。我更倾向于通过某个方面处理异常,但您的解决方案也是可行的。
答案 2 :(得分:0)
走得太远了?
请注意,您在每个方法中添加对PerformServiceOperation()的调用,以便将异常处理移出该方法。除非您从重复的异常处理中节省了大量的维护时间,否则您可能会走得很远。
但是,如果已经有一种方法可以向您的选择框架(WCF)添加异常处理来处理未处理的异常,那么以这种方式执行它并不会太过分。
使用aspect oriented programming(AOP)来记录异常的方法拦截非常简单。但是出于这个原因,切换到AOP肯定太过分了......