C ++函数/方法设计的良好实践

Good practice in C++ function/method design

我对C++函数/方法的设计有如下困惑:

1。

1
2
3
4
5
6
7
8
9
class ArithmeticCalculation
{
private:
    float num1_;
    float num2_;
    float sum_;

    void addTwoNumbers();
};

2。

1
2
3
4
5
class ArithmeticCalculation
{
private:
    float addTwoNumbers(float num1, float num2);
};

在1.中,我们基本上可以声明一个类变量,void addtwonumbers()将实现它并分配给类变量(sum_)。我发现使用1。更干净,但使用2。看起来功能使用更直观。

考虑到函数/方法不仅仅局限于这个基本的加法功能,哪一个实际上是最好的选择——我的意思是一般情况下,如何决定使用返回或简单的void?


这两个函数的主要区别在于,第二个函数是无状态的*,而第一个函数是有状态的。在其他条件相同的情况下,无状态方法是首选的,因为它使类的用户在系统中使用类时具有更大的灵活性。例如,无状态函数是可重入的,而依赖状态的函数可能需要使用它们的代码采取其他措施来防止不正确的使用。

只有可重入性是一个很大的原因,可以尽可能选择无状态函数。但是,在某些情况下,保持状态变得更经济——例如,当您使用构建器设计模式时。

尽可能保持函数无状态的另一个重要优点是调用序列变得更可读。依赖状态的方法调用由以下部分组成:

  • 在调用前设置对象
  • 打电话
  • 获取调用结果(可选)

与由三部分组成的设置调用get result sequence相比,代码的人工读卡器读取使用带有参数传递的函数调用的调用要容易得多。

有些情况下,你必须有状态,例如,当你想推迟行动。在这种情况下,参数由代码的一部分提供,而计算则由代码的另一部分启动。在您的示例中,一个函数调用set_num1set_num2,另一个函数稍后调用addTwoNumbers。在这种情况下,您可以保存对象本身的参数,或者使用延迟参数创建单独的对象。

*这只是基于成员函数签名的假设。第二个函数获取它所需要的所有数据作为参数,并将值返回给调用方;显然,实现可能选择添加一些状态,例如通过保存最后一个结果来添加,但这对于addTwoNumbers函数是不常见的,因此我假定您的代码不会这样做。


第一个函数的意义不大。什么数字?结果如何?这个名字既没有描述预期的副作用,也没有描述相关数字的来源。

第二个函数使它非常清楚发生了什么,结果在哪里,以及如何使用该函数。

您的函数应该努力根据函数签名来传达它们的意图。如果这还不够,您需要添加注释或文档,但是任何数量的注释或文档都不能铺平误导或混淆的签名。

想想你的函数的职责是什么,以及它在命名事物时有什么期望。例如:

1
void whatever(const int);

这个函数是做什么的?你能在不看代码或文档的情况下猜测吗?

与相同的函数相比,给出了一个更有意义的名称:

1
void detonateReactor(const int countdownTimeInSeconds);

现在看来很清楚它会起什么作用,也很清楚它会有什么副作用。


对于第一种选择,您可能会想到类似的事情:

1
2
3
4
5
6
struct Adder {
    float sum;
    float a;
    float b;
    void addNumbers(){ sum = a+b; }
};

使用方法如下:

1
2
3
4
5
6
Adder adder;
adder.a = 1.0;
adder.b = 2.0;
adder.addNumbers();
std::cout << adder.sum <<"
"
;

当你真的想要这样做的时候,没有一个好的理由可以这样做:

1
2
3
4
float addTwoNumbers(float a,float b) { return a+b; }

std::cout << addTwoNumbers(1.0,2.0) <<"
"
;

不是每件事都必须在课堂上进行。实际上不是所有的东西都应该在一个类里面(C++ + NavaJava)。如果你需要一个加两个数的函数,那么写一个加两个数的函数,不要想得太多。