10

Are there objective, supportable software-engineering arguments for or against modifying the values of by-value parameters in the body of a function?

A recurring spat (mostly in good fun) on my team is whether or not parameters passed by value should be modified. A couple members of the team are adamant that parameters should be never assigned to, so that the value originally passed to the function can always be interrogated. I disagree and hold that parameters are nothing more than local variables initialized by the syntax of calling the method; if the original value of a by-value parameter is important than a local variable can be declared to explicitly store this value. I am not confident that either of us has very good support for our position.

Is this a non-resolvable religious conflict, or are there good, objective software engineering reasons in either direction?

Note: The question of principle remains regardless of the implementation details of the particular language. In JavaScript, for example, where the arguments list is always dynamic, parameters can be regarded as syntactic sugar for local variable initialization from the arguments object. Even so, one could treat parameter-declared identifiers as "special" because they still capture the passing of information from the caller to the callee.

4
  • It is still language-specific; arguing that it is language-agnostic because "it is so, from my (favorite language)'s point of view" is a form of invalid argument. A second reason why it is language-specific is because a question that does not have universal answer for all languages are subject to each language's cultures and norms; in this case, different language-specific norms result in different answers to this question.
    – rwong
    Apr 16, 2018 at 15:13
  • The principle that your team members are trying to teach you is "one variable, one purpose." Unfortunately, you're not going to find definitive proof either way. For what it's worth, I don't remember ever co-opting a parameter variable for some other purpose, nor do I remember ever considering it. It never occurred to me that it might a good idea, and I still don't think it is. Apr 16, 2018 at 16:13
  • I thought this must have been asked before, but the only dupe I could find is this older SO question.
    – Doc Brown
    Apr 16, 2018 at 20:13
  • 3
    It is considered less error prone for a language to prohibit argument mutation, just as functional language designers consider “let” assignments to be less error prone. I doubt anyone has proven this assumption using a study. Apr 16, 2018 at 22:10

3 Answers 3

15

I disagree and hold that parameters are nothing more than local variables initialized by the syntax of calling the method

I adopt a third position: parameters are just like local variables: both should be treated as immutable by default. So variables are assigned once and then only read from, not altered. In the case of loops, for each (x in ...), the variable is immutable within the context of each iteration. The reasons for this are:

  1. it makes the code easier to "execute in my head",
  2. it allows more descriptive names for the variables.

Faced with a method with a bunch of variables that are assigned and then do not change, I can focus on reading the code, rather than trying to remember the current value of each of those variables.

Faced with that same method, this time with less variables, but that change value all the time, I now have to remember the current state of those variables, as well as working at what the code is doing.

In my experience, the former is far easier on my poor brain. Other, cleverer, folk than me may not have this problem, but it helps me.

As for the other point:

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

versus

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

Contrived examples, but to my mind it's much clearer as to what's going on in the second example due to the variable names: they convey far more information to the reader than that mass of r's do in the first example.

10
  • "local variables .... should be treated as immutable by default." - What? Apr 16, 2018 at 15:18
  • 1
    It's pretty hard to run for loops with immutable variables. Encapsulating the variable in the function isn't sufficient for you? If you're having trouble following your variables in a mere function, perhaps your functions are too long. Apr 16, 2018 at 15:23
  • @RobertHarvey for (const auto thing : things) is a for loop, and the variable it introduces is (locally) immutable. for i, thing in enumerate(things): also doesn't mutate any locals
    – Caleth
    Apr 16, 2018 at 16:14
  • 3
    This is IMHO in general a very good mental model. However, one thing I would like to add: often it is acceptable to initialize a variable in more than one step and treat it as immutable afterwards. This is not against the general strategy presented here, just against following this always literally.
    – Doc Brown
    Apr 16, 2018 at 19:58
  • 3
    Wow, reading these comments, I can see that many people on here have obviously never studied the functional paradigm. Apr 17, 2018 at 3:36
4

Is this a non-resolvable religious conflict, or are there good, objective software engineering reasons in either direction?

That's one thing I really like about (well written) C++: you can see what to expect. const string& x1 is a constant reference, string x2 is a copy and const string x3 is a constant copy. I know what will happen in the function. x2 will be changed, because if it weren't, there would be no reason to make it non-const.

So if you have a language that allows it, declare what you are about to do with the parameters. That should be the best choice for everybody involved.

If your language does not allow this, I'm afraid there is no silver bullet solution. Both is possible. The only guideline is the principle of least surprise. Take one approach and go with it throughout your code base, don't mix it.

5
  • Another good thing is that int f(const T x) and int f(T x) are only different in the function-body. Apr 16, 2018 at 16:20
  • 3
    A C++ function with a parameter like const int x just to make clear x is not changed inside? Looks like a very strange style to me.
    – Doc Brown
    Apr 16, 2018 at 20:04
  • @DocBrown I'm not judging either style, I'm just saying that someone who thinks parameters should not be changed can make the compiler guarantee it and somebody that thinks changing them is fine can do so as well. Both intentions can be clearly communicated through the language itself and that is a great advantage over a language that cannot guarantee it either way and where you have to rely on arbitrary guidelines and hope everybody read them and conforms to them.
    – nvoigt
    Apr 17, 2018 at 7:45
  • @nvoigt: if someone prefers to modify a ("by-value") function parameter, he would simply strip the const away from the parameter list if it hinders him. So I don't think this answers the question.
    – Doc Brown
    Apr 17, 2018 at 9:39
  • @DocBrown Then it's no longer const. It's not important whether it's const or not, the important thing is that the language includes a way to communicate that to the developer without a doubt. My answer is it does not matter, as long as you communicate it clearly, which C++ makes easy, others may not and you need conventions.
    – nvoigt
    Apr 17, 2018 at 10:05
1

Yeah, it's a "religious conflict", except that God doesn't read programs (as far as I know), so it's downgraded to a readability conflict that becomes a matter of personal judgement. And I've certainly done it, and seen it done, both ways. For example,

void propagate(OBJECT *object, double t0, double dt) {
  double t = t0; /* local copy of t0 to avoid changing its value */
  while ( whatever) {
    timestep_the_object(...);
    t += dt; }
  return; }

So here, just because of the argument name t0, I'd likely choose not to change its value. But suppose instead, we merely changed that argument name to tNow, or something like that. Then I'd much-more-than-likely forgot about a local copy and just write tNow += dt; for that last statement.

But contrariwise, suppose I knew that the client for whom the program was written is about to hire some new programmers with very little experience, who'd be reading and working on that code. Then I'd likely worry about confusing them with pass-by-value versus -reference, while their minds are 100% busy trying to digest all the business logic. So in this kind of case I'd always declare a local copy of any argument that will be changed, regardless of whether or not it's more or less readable to me.

Answers to these kinds of style questions emerge with experience, and rarely have a canonical religious answer. Anybody who thinks there's one-and-only-one answer (and that he knows it:) probably needs some more experience.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.