13

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.

6 Answers6

20

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.

David Arno
  • 39,599
  • 9
  • 94
  • 129
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.

nvoigt
  • 9,230
  • 3
  • 30
  • 31
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.

1

In C++, a parameter can have type (for example) int i, const int i, int& i, and const int& i. References obviously work different, but if you want to prevent assignment to i, you just make it const int i. If it’s not const int, it’s fair game for modifying.

Importantly it doesn’t make any difference to the caller. The object that you see as a parameter is copied into a local variable by the function behind your back, and you can only modify the local variable and not the parameter itself. (Of course the copying doesn’t happen if the compiler can prove it is not needed).

gnasher729
  • 49,096
1

I very rarely find myself re-assigning parameters in the method body. That's not because I have an objection to that per-se, but because of naming considerations.

In a well-designed method, parameter names are chosen so as to make sense outside the context of the method, so that they are easier to reason about from the perspective of the method caller.

In my experience, a parameter name chosen this way rarely lends itself to being appropriate to modify in the method body. Adapting David Arno's example:

public double calculateCircleArea(double radius)
{
    radius = radius * radius;
    radius = Math.PI * radius;
    return radius;
}

This screams at me, not because it's reassigning the parameter value, but because the name radius no longer makes sense. I'm calculating the area, but returning a variable named radius?! That is confusing.

Mutable variables in a function have their place - but I'd expect their name to reflect that it's a calculation in progress, e.g. runningTotal. Such a name would be a poor choice for a parameter to that method!

PMah
  • 159
  • 5
1

Because of pointers. A pointer maybe passed by value but the object can be mutable.

To stay in the contrived example:

public double GetArea(Circle C)
{
    C.radius = C.radius * C.radius;
    C.radius = Math.PI * C.radius;
    return C.radius;
}

Now you have changed the object without changing your parameter.

It's just a healthy habit in the end.

Pieter B
  • 13,310