我从没想过我需要任何东西(因为宏很糟糕,对吧?),但事实证明我做到了。
我发现自己在我的代码中一遍又一遍地重复以下模式,数十次甚至数百次:
object Function() {
this.Blah = this.Blah.Resolve(...);
if(this.Blah == null)
return null;
if(this.Blah.SomeFlag)
return this.OtherFunction();
// otherwise, continue processing...
}
我想写这样的东西:
object Function() {
this.Blah = this.Blah.Resolve(...);
VerifyResolve(this.Blah);
// continue processing...
}
除了模式包含条件return
以外,这意味着我无法分解函数。我不得不巧妙地改变模式,因为我无法进行简单的搜索,所以找到所有实例都很痛苦。
如何避免不必要地重复自己,并且更容易对此模式进行任何更改?
答案 0 :(得分:3)
如果您经常发现必须查看null
,则可能需要使用Null Object Pattern。实质上,您创建一个表示null结果的对象。然后,您可以返回此对象而不是null
。然后,您必须调整其余代码以包含null对象的行为。沿着这些方向的东西也许是:
MyClass Function()
{
this.Blah = this.Blah.Resolve(...);
VerifyResolve(this.Blah);
// continue processing...
}
MyClass VerifyResolve(MyClass rc)
{
// ...
return rc.Blah.SomeFlag ? rc.OtherFunction() : rc;
}
NullMyClass : MyClass {
public override bool SomeFlag { get { return false; } }
}
答案 1 :(得分:1)
你可以这样说:
object Function()
{
this.Blah = this.Blah.Resolve(...);
object rc;
if (!VerifyResolve(out rc)
return rc;
// continue processing...
}
bool VerifyResolve(out object rc)
{
if(this.Blah == null)
{
rc = null;
return true;
}
if(this.Blah.SomeFlag)
{
rc = this.OtherFunction();
return true;
}
rc = null;
return false;
}
答案 2 :(得分:1)
也许更整洁:
if (!TryResolve(this.Blah)) return this.Blah;
其中TryResolve将this.Blah的值设置为null或this.OtherFunction并返回适当的bool
答案 3 :(得分:1)
没有冒犯,但那里有几个代码味道:
this.Blah = this.Blah.Resolve(...);
这一行特别会让我开始重新思考的过程。
我遇到的主要问题是对象返回类型以及通过调用该属性上的方法返回的值的属性赋值。这些都闻起来就像你已经搞砸了某个地方的继承而最终得到了一个非常有状态的系统,这既是一个测试的错误,也是一个难以维护的问题。
也许最好重新考虑而不是试图用黑客和技巧来回避这个问题:我通常会发现,如果我试图用像Macros这样的功能滥用语言,那么我的设计需要工作!
修改
好的,所以添加信息也许这不是一种气味,但我仍然建议如下:
class ExampleExpr{
StatefulData data ... // some variables that contain the state data
BladhyBlah Blah { get; set; }
object Function(params) {
this.Blah = this.Blah.Resolve(params);
....
}
}
这段代码令人担忧,因为它强制采用完全基于状态的方法,输出取决于事先发生的事情,因此需要特定的步骤进行复制。这是一个痛苦的考验。另外,如果你两次调用Function(),就不能保证Blah会在不知道它首先处于什么状态的情况下会发生什么。
class ExampleExpr{
StatefulData data ... // some variables that contain the state data
object Function(params) {
BlahdyBlah blah = BlahdyBlah.Resolve(params, statefulData);
}
}
如果我们使用工厂式方法,每当我们被提供一组参数时返回一个具有特定信息的新实例,我们就消除了一个使用有状态数据的地方(即BladhyBlah实例现在重新使用特定参数集在每次调用时创建。)
这意味着我们可以通过简单地使用特定的Setup()函数调用Function(params)来复制测试中的任何功能,以创建statefulData和一组特定的参数。
理论上这效率较低(因为每次工厂调用都会创建一个新的BlahdyBlah)但是可以使用特定数据缓存BlahdyBlah的实例并在工厂调用之间共享它们(假设它们没有其他影响它们的方法)内部状态)。然而,它的维护更加友好,从测试的角度来看,它完全扼杀了有状态数据的屁股。
这也有助于消除原来的问题,因为当我们不依赖于内部实例变量时,我们都可以从Function(params)外部解析(params,statefulData),如果不是blah则根本不调用Function(params) == null或blah.SomeFlag == SomeFlag.Whatever。因此,通过在方法之外移动它,我们不再需要担心返回。
希望这是一个正确的球场,很难确切地知道在一个小例子中推荐什么,就像这里的困难/抽象问题一样。
答案 4 :(得分:0)
最好使用Verify Resolve扩展方法来扩展Blah类。 如果您拥有Blah源,那么最好创建一些IVerifyResolveble接口并让Blah实现它。
答案 5 :(得分:0)
object Function() {
this.Blah = this.Blah.Resolve(...);
object result;
if (VerifyResolve(this.Blah, out result))
return result;
// continue processing...
}
答案 6 :(得分:0)
这很麻烦,烦人,而且冗长,但这就是我必须要做的事情。可能没有更好的方法。
另一种方法是制作一个结构,其可空性很重要。
struct Rc
{
internal object object;
internal Rc(object object) { this.object = object; }
}
object Function()
{
this.Blah = this.Blah.Resolve(...);
Rc? rc = VerifyResolve();
if (rc.HasValue)
return rc.Value.object;
// continue processing...
}
Rc? VerifyResolve()
{
if(this.Blah == null)
{
return new Rc(null);
}
if(this.Blah.SomeFlag)
{
return new Rc(this.OtherFunction());
}
return null;
}