避免过多的功能参数:类居中或功能为中心的方法?
-
22-09-2019 - |
题
你会如何解决,围绕传递太多的参数如下恶意代码?
void helper1(int p1, int p3, int p5, int p7, int p9, int p10) {
// ...
}
void helper2(int p1, int p2, int p3, int p5, int p6, int p7, int p9, int p10) {
// ...
}
void foo(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8,
int p9, int p10) {
helper1(p1, p3, p5, p7, p9, p10);
helper2(p1, p2, p3, p5, p6, p7, p9, p10);
}
我看到两种不同的方法:
<强>方法1:将所有的功能在一个类强>
class Foo {
private:
int p1, p2, p3, p4, p5, p6, p7, p8, p9, p10;
void helper1() {}
void helper2() {}
public:
void foo() {
helper1();
helper2();
}
// add constructor
};
<强>方法2:就传递参数作为一类强>
struct FooOptions {
int p1, p2, p3, p4, p5, p6, p7, p8, p9, p10;
};
void helper1(const FooOptions& opt) {
// ...
}
void helper2(const FooOptions& opt) {
// ...
}
void foo(const FooOptions& opt) {
helper1(opt);
helper2(opt);
}
哪些方法的优点和缺点?
方法1的一个优点是, - 如果你做出helper
功能虚拟 - 那么可以继承,并重载它们,加入灵活性。不过,在我的情况下(即我给玩具迷你示例以外),例如助手常常模板,所以它们不能被虚拟反正。
方法2的一个优点是,该辅助功能可以很容易地从其它功能被调用,太
(这问题是相关的,但不讨论这两个替代方案。)
解决方案
短答案:
新年快乐!我会避免选项#1只与选项#2去,如果该参数可以远离你的功能被分成清晰的逻辑组是否有意义。
长回答
当你从同事描述我看到的功能的例子很多。我的事实,这是一个糟糕的代码气味同意你的看法。然而,分组参数为一类,所以您不必相当随意地传递参数,并决定将其基于这些辅助功能会导致更坏的气味组。你要问自己,如果你能提高可读性和理解其他后你来了。
calcTime(int p1, int p2, int p3, int p4, int p5, int p6) {
dist = calcDistance( p1, p2, p3 );
speed = calcSpeed( p4, p5, p6 );
return speed == 0 : 0 ? dist/speed; }
有,你可以组的事情要更容易理解,因为有一个明显的区别之间的参数。然后我建议的方法#2。
在另一方面,在我已经被移交经常代码如下:
calcTime(int p1, int p2, int p3, int p4, int p5, int p6) {
height = height( p1, p2, p3, p6 );
type = getType( p1, p4, p5, p6 );
if( type == 4 ) {
return 2.345; //some magic number
}
value = calcValue( p2, p3, type ); //what a nice variable name...
a = resetA( p3, height, value );
return a * value; }
这让你有一种感觉,这些参数都算不上友好分手成有意义的事情类明智的。相反,你会更好担任翻转乾坤,如
calcTime(Type type, int height, int value, int p2, int p3)
,然后调用它
calcTime(
getType( p1, p4, p5, p6 ),
height( p1, p2, p3, p6 ),
p3,
p4 );
这可能发凉你的脊椎作为小小的声音在你的头尖叫“干,干,干的!”哪一个是更易读并且因此维护?
选项1是一个不走在我的头上,因为是一个很好的可能性有人会忘记的参数集合中的一个。这可能很容易导致难以检测通过简单的单元测试错误。 YMMV。
其他提示
我重写了类和结构,以便它们可以被分析如下:
class Point {
private:
int x, y
void moveUp() {}
void moveRight() {}
public:
void moveUpAndRight() {
moveUp();
moveRight();
}
// add constructor
};
struct Point {
int x, y;
};
void moveUp {}
void moveRight {}
void moveUpAndRight {
moveUp {}
moveRight {}
}
使用类,您可以根据您的对象而定。使用点对象代码将使用一个标准的接口来移动点。如果坐标系统的变化和点类被修改以检测的坐标系是在,这将允许相同的接口在多个使用坐标系(没有违反任何东西)。在这个例子中是有意义的去与方法1。
如果为组织目的而访问您的只是将相关的数据...
struct Dwarves {
int Doc, Grumpy, Happy, Sleepy, Bashful, Sneezy, Dopey;
}
void killDwarves(Dwarves& dwarves) {}
void buryDwarves(Dwarves& dwarves) {}
void disposeOfDwarves(Dwarves& dwarves) {
killDwarves(dwarves);
buryDwarves(dwarves);
}
然后是有意义的去与方法2。
这是一个面向对象的设计选择,以便有多个正确的解决方案,并从我们给出的是什么很难给出一个明确的答案。
如果P1,P2,等等......其实只是选项(标志,等...),并完全无关的对方,然后我会去的第二个选项。
如果他们中的一些往往是永远在一起的话,说不定还有一些类等出现在这里,我将与第一个选项去,但可能不会在一个大的类(你可能实际上有几个类来创建 - - 这是很难不实际的参数的名字)说。
class Something {
int p1, p3, p5 ...;
void helper1a();
};
class SomethingElse {
int p7, p9, p10 ...;
void helper1b();
};
class Options {
int p2, p4, ...
};
void foo(Something &s, SomethingElse & else, Options &options) {
helper2(options);
s.helper1a();
else.helper1b();
}
选项#2。
然而,如果你仔细想想领域,你可能会发现,有参数值的有意义的分组,他们可以在您的域名映射到一些实体。
然后,您可以创建该对象的实例,它传递到您的程序。
典型地,这威力类似应用程序对象,或者一个会话对象,等等。
把它们放到一个类的方法没有“感觉”是正确的。这似乎是一个企图改造旧代码为找喜欢的事,其实不然。随着时间的推移,有可能向代码的“面向对象”的风格移动,但似乎越有可能将结束是两种模式的一个糟糕的组合。但是,这主要是基于我会用一些非面向对象的代码,我的工作有发生什么事的想法。
因此,那些两个选择之间,我觉得结构的选择是一个好一点。它的叶子灵活开放的另一个功能是能够在参数打包成一个结构和调用的辅助功能之一。但这种方法对我来说好像它与自我记录方面的问题。如果有必要将呼叫从其他位置添加到这个辅助函数,它并不像清楚哪些参数必须传递给它。
因此,虽然感觉就像一个数量的下降票来我的方式,我觉得最好的办法就是不要改变它。一个好的编辑器会显示您的参数列表,当你在一个新的呼叫类型到的功能之一,这使得它非常简单的得到它的权利。除非它是在一个高流量区域的成本是不是所有的伟大。并用64位环境中,它是更少因为多个参数(是4?)在寄存器中通常被发送。
这里是一个相关的问题谈论这与许多应答称重的英寸
我不知道。这真的取决于什么参数的是即可。有没有神奇的“化妆20函数参数消失”,你可以调用的设计模式。你能做的最好的是重构你的代码。根据什么的参数表示,这可能是有意义的组其中一些为参数类,或者把一切都成一个整体的阶级,或分割整个事情出成多个类别。一种选择,其可以是工作某种形式的钻营,传递对象比特的时间,每一次产生一个新的对象在其上的下一个呼叫可以被调用,通过下一批的参数。这尤其使得如果某些参数经常留在调用相同的意义。
这方面的一个简单的变体可能是一类,其中一些参数传入构造(通常留横跨整批调用相同的那些),以及其他包装的一切,必须提供的那些每个呼叫,因为它们可以不断地变化,在实际的函数调用(可能一个重载operator()
)被传递。
不知道实际的意的代码,就很难说如何才能最好地重构。
不使用类作为纯数据存储,并且不使用字段将数据传递到方法。通常,如果一个方法需要的参数太多它确实太多了,所以你应该从中提取功能集成到单独的方法。也许你可以提供您的方法更具体的例子,所以我们可以从一个更大的角度来讨论它。