[英]Macro-like structures in C# ==OR== How to factor out a function containing 'return'
我从没想过我需要任何东西(因为宏很糟糕,对吧?),但事实证明我做到了。
我发现自己在我的代码中一遍又一遍地重复以下模式,数十次,甚至数百次:
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
s这一事实外,我无法将函数分解出来。 我不得不巧妙地改变模式,因为我无法进行简单的搜索,所以查找所有实例都很痛苦。
如何避免不必要地重复自己,并且更容易对此模式进行任何更改?
如果您经常发现必须检查null
,则可能需要使用Null对象模式 。 实质上,您创建一个表示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; } }
}
你可以这样说:
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;
}
也许更整洁:
if (!TryResolve(this.Blah)) return this.Blah;
其中TryResolve将this.Blah的值设置为null或this.OtherFunction并返回适当的bool
没有冒犯,但那里有几个代码味道:
this.Blah = this.Blah.Resolve(...);
这条线特别会让我开始重新思考的过程。
我遇到的主要问题是对象返回类型以及通过调用该属性上的方法返回的值的属性。 这些都闻起来就像你已经搞砸了某个地方的继承而最终得到了一个非常有状态的系统,这既是一个测试的bugger,也是一个难以维护的。
也许最好重新思考而不是试图使用黑客和技巧来回避这个问题:我通常会发现,如果我试图用像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和一组特定的params。
理论上这效率较低(因为在每次工厂调用时都会创建一个新的BlahdyBlah)但是可以使用特定数据缓存BlahdyBlah的实例并在工厂调用之间共享它们(假设它们没有影响其内部状态的其他方法) 。 然而,它的维护更加友好,从测试的角度来看,完全可以解决有状态数据的问题。
这也有助于消除原始问题,因为当我们不依赖于内部实例变量时,我们可以从Function(params)外部解析(params,statefulData),如果blah == null,则不会调用Function(params)或者blah.SomeFlag == SomeFlag.Whatever。 因此,通过在方法之外移动它,我们不再需要担心返回。
希望这是一个正确的球场,很难确切地知道在一个小例子中推荐什么,就像这里的困难/抽象问题的情况一样。
最好使用VerifyResolve extantion方法来扩展Blah类。 如果您拥有Blah源,那么最好创建一些IVerifyResolveble接口并让Blah实现它。
object Function() {
this.Blah = this.Blah.Resolve(...);
object result;
if (VerifyResolve(this.Blah, out result))
return result;
// continue processing...
}
这很麻烦,很烦人,而且很冗长,但这就是我必须要做的事情。 可能没有更好的方法。
另一种方法是制作一个结构,其可空性很重要。
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;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.