是否有理由不修改按值传递的参数的值?


9

是否有客观的,可支持的软件工程论据来支持或反对在函数主体中修改按值参数的值?

我团队经常发生的争吵(主要是很好玩)是是否应修改按值传递的参数。团队中的几个成员坚决决不应该将参数分配给它们,以便始终可以查询最初传递给该函数的值。我不同意并认为参数不过是通过调用方法的语法初始化的局部变量。如果按值参数的原始值很重要,则可以声明局部变量以显式存储该值。我不相信我们两个人都能很好地支持我们的立场。

这是无法解决的宗教冲突,还是在任一方向上都有好的客观的软件工程理由?

注意:原则问题仍然存在,无论特定语言的实现细节如何。例如,在JavaScript中,参数列表始终是动态的,参数可以视为从arguments对象初始化局部变量的语法糖。即使这样,也可以将参数声明的标识符视为“特殊”标识符,因为它们仍然捕获信息从调用者到被叫者的传递。


它仍然是特定于语言的;认为它与语言无关,因为“从我(最喜欢的语言)的角度来看是如此”是一种无效的论证形式。之所以要使用特定语言是第二个原因,是因为一个并非所有语言都具有通用答案的问题要服从每种语言的文化和规范。在这种情况下,不同的语言特定规范会导致对该问题的不同答案。
rwong

您的团队成员试图教给您的原则是“一个变量,一个目的”。不幸的是,您不会以任何方式找到确定的证据。出于其价值,我不记得曾经为其他目的而选择参数变量,也不记得曾经考虑过。我从未想到这可能是个好主意,但我仍然不认为这是个好主意。
罗伯特·哈维

我以为以前一定要问过这个问题,但是我唯一能找到的就是这个较旧的SO问题
布朗

3
语言禁止参数突变被认为不太容易出错,就像功能语言设计人员认为“ let”分配不太容易出错一样。我怀疑有人通过研究证明了这一假设。
Frank Hileman '18

Answers:


15

我不同意并认为参数仅是通过调用方法的语法初始化的局部变量

我采取第三种立场:参数就像局部变量:默认情况下,它们都应被视为不可变的。因此,变量只分配一次,然后只能读取,不能更改。在循环的情况下for each (x in ...),该变量在每次迭代的上下文中都是不可变的。原因如下:

  1. 它使代码更容易“在我的脑海中执行”,
  2. 它允许使用更具描述性的变量名称。

面对带有一堆已分配但不会更改的变量的方法,我可以专注于阅读代码,而不是试图记住每个变量的当前值。

面对相同的方法,这次变量较少,但是一直在改变值,因此我现在必须记住这些变量的当前状态以及代码的工作方式。

以我的经验,在我可怜的大脑上,前者要容易得多。除了我之外,其他一些更聪明的人可能没有这个问题,但这对我有帮助。

至于另一点:

double Foo(double r)
{
    r = r * r;
    r = Math.Pi * r;
    return r;
}

double Foo(int r)
{
    var rSquared = r * r;
    var area = Math.Pi * rSquared;
    return area;
} 

人为的示例,但是在我看来,由于变量名的原因,第二个示例中发生的事情要清楚得多:r与第一个示例中相比,它们向读者传达的信息要多得多。


“默认情况下,局部变量...应被视为不可变的。” - 什么?
whatsisname

1
for使用不可变变量运行循环非常困难。将变量封装在函数中还不够吗?如果您在单纯的函数中无法跟踪变量,则可能是函数太长了。
罗伯特·哈维

@RobertHarvey for (const auto thing : things)是一个for循环,它引入的变量是(局部)不可变的。for i, thing in enumerate(things):也不会变异任何当地人
Caleth

3
总的来说,这是恕我直言,非常好的心理模型。但是,我想补充一件事:通常可以在一个以上的步骤中初始化变量,然后将其视为不可变的。这与这里提出的一般策略不符,只是始终遵循字面意义。
布朗

3
哇,阅读这些评论,我可以看到很多人显然从未研究过功能范式。
Joshua Jones

4

这是无法解决的宗教冲突,还是在任一方向上都有好的客观的软件工程理由?

我真的很喜欢(写得很好)C ++的一件事:您可以看到期望什么。const string& x1是一个常量引用,string x2是一个副本,并且const string x3是一个常量副本。我知道函数会发生什么。x2 将会被更改,因为如果没有,就没有理由将其设为非常量。

因此,如果您使用允许的语言,则声明您将要使用的参数。这应该是每个参与人员的最佳选择。

如果您的语言不允许这样做,恐怕没有解决方案。两者都有可能。唯一的指导原则是最少惊讶的原则。采用一种方法,并在整个代码库中使用它,请不要混合使用。


另一个好处是,int f(const T x)并且int f(T x)仅在功能体方面有所不同。
Deduplicator

3
带有参数的C ++函数(例如const int x只是为了清楚x)在内部不会更改?对我来说看起来很奇怪。
布朗

@DocBrown我不是在判断哪种样式,我只是说认为不应该更改参数的人可以使编译器保证它,而认为更改它们就可以的人也可以这样做。可以通过语言本身清楚地传达这两种意图,这与不能保证以任何方式和在任何地方都必须依靠任意指导原则并希望每个人都能阅读并遵守它们的语言相比,这是一个很大的优势。
nvoigt

@nvoigt:如果某人喜欢修改(“按值”)函数参数,那么const如果它妨碍了他,他只会从参数列表中删除该参数。因此,我认为这不能回答问题。
布朗

@DocBrown然后不再是常量。它是否是const并不重要,重要的是该语言包含了一种毫无疑问地与开发人员进行通信的方法。我的答案是没关系,只要您清楚地进行交流即可,即哪个C ++容易实现,而其他C ++则不那么容易,并且您需要约定。
nvoigt

1

是的,这是一个“宗教冲突”,只是神不阅读程序(据我所知),因此它被降级为可读性冲突,这是个人判断的问题。而且我当然已经做到了,并且看到它已经完成了。例如,

无效传播(OBJECT * object,double t0,double dt){
  双倍t = t0; / * t0的本地副本,以避免更改其值* /
  而(无论){
    timestep_the_object(...);
    t + = dt; }
  返回; }

所以在这里,仅由于参数名称t0,我很可能选择不更改其值。但是假设相反,我们只是将参数名称更改为tNow或类似的名称。然后,我几乎忘了本地副本,只写了tNow + = dt; 对于最后的陈述。

但是相反,假设我知道为该程序编写代码的客户将要聘用一些经验很少的新程序员,他们正在阅读和处理该代码。然后,我可能会担心将它们与“按值传递”和“-引用”混淆,而他们的想法却100%忙于尝试消化所有业务逻辑。因此,在这种情况下,我总是会声明将要更改的任何参数的本地副本,而不管它对我而言是否可读。

对这些样式问题的答案是随着经验而出现的,很少有规范的宗教答案。任何认为只有一个答案(并且他知道的答案)的人都可能需要更多的经验。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.