Есть ли причина не изменять значения параметров, передаваемых по значению?

9

Существуют ли объективные, поддерживаемые аргументы в пользу разработки программного обеспечения за или против изменения значений параметров в теле функции?

Повторяющаяся ссора (в основном в хорошем шутке) в моей команде заключается в том, следует ли изменять параметры, передаваемые по значению. Пара членов команды непреклонна, что параметры никогда не должны назначаться, так что всегда можно запросить значение, первоначально переданное функции. Я не согласен и считаю, что параметры - это не более чем локальные переменные, инициализируемые синтаксисом вызова метода; если исходное значение параметра по значению важно, то может быть объявлена ​​локальная переменная для явного хранения этого значения. Я не уверен, что кто-либо из нас очень хорошо поддерживает нашу позицию.

Это неразрешимый религиозный конфликт или есть хорошие, объективные причины для разработки программного обеспечения в любом направлении?

Примечание: принципиальный вопрос остается независимо от деталей реализации конкретного языка. Например, в JavaScript, где список аргументов всегда динамический, параметры можно рассматривать как синтаксический сахар для инициализации локальной переменной из argumentsобъекта. Несмотря на это, идентификаторы, объявленные параметрами, можно рассматривать как «особые», поскольку они по-прежнему фиксируют передачу информации от вызывающего к вызываемому.

Джошуа Хониг
источник
Это все еще зависит от языка; утверждение, что это не зависит от языка, потому что «это так, с моей (любимой точки зрения) точки зрения» является формой недопустимого аргумента. Вторая причина, по которой он зависит от языка, заключается в том, что вопрос, который не имеет универсального ответа для всех языков, зависит от культур и норм каждого языка; в этом случае разные языковые нормы приводят к разным ответам на этот вопрос.
rwong
Принцип, которому члены вашей команды пытаются научить вас, - «одна переменная, одна цель». К сожалению, вы не найдете окончательного доказательства в любом случае. Что бы это ни стоило, я не помню, чтобы когда-либо использовал переменную параметра для какой-то другой цели, и я не помню, чтобы когда-либо рассматривал это. Мне никогда не приходило в голову, что это может быть хорошей идеей, и я до сих пор не думаю, что это так.
Роберт Харви
Я думал, что это, должно быть, задавали раньше, но единственная глупость, которую я смог найти, это старый вопрос SO .
Док Браун
3
Для языка считается менее подверженным ошибкам запрет на изменение аргументов, так же как разработчики функционального языка считают, что «разрешенные» назначения менее подвержены ошибкам. Я сомневаюсь, что кто-либо подтвердил это предположение с помощью исследования.
Фрэнк Хайлеман

Ответы:

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, которая в первом примере.

Дэвид Арно
источник
msgstr "локальные переменные .... должны рассматриваться как неизменяемые по умолчанию." - Какая?
whatsisname
1
Довольно сложно запускать forциклы с неизменяемыми переменными. Инкапсуляция переменной в функции не достаточно для вас? Если у вас возникают проблемы с отслеживанием переменных в простой функции, возможно, ваши функции слишком длинные.
Роберт Харви
@RobertHarvey for (const auto thing : things)- это цикл for, и вводимая переменная является (локально) неизменной. for i, thing in enumerate(things):также не мутирует никаких местных жителей
Caleth
3
Это вообще ИМХО очень хорошая ментальная модель. Однако я хотел бы добавить одну вещь: часто допустимо инициализировать переменную более чем за один шаг и впоследствии обращаться с ней как с неизменяемой. Это не против общей стратегии, представленной здесь, просто против следования этому буквально всегда.
Док Браун
3
Ничего себе, читая эти комментарии, я вижу, что многие здесь, очевидно, никогда не изучали функциональную парадигму.
Джошуа Джонс
4

Это неразрешимый религиозный конфликт или есть хорошие, объективные причины для разработки программного обеспечения в любом направлении?

Это одна вещь, которая мне действительно нравится в (хорошо написанном) C ++: вы можете видеть, чего ожидать. const string& x1является постоянной ссылкой, string x2является копией и const string x3является постоянной копией. Я знаю, что произойдет в функции. x2 будет изменено, потому что, если бы не было, не было бы причины делать его неконстантным.

Поэтому, если у вас есть язык, который позволяет это, объявите, что вы собираетесь делать с параметрами. Это должно быть лучшим выбором для всех участников.

Если ваш язык не позволяет этого, я боюсь, что нет решения серебряной пули. И то и другое возможно. Единственное руководство - принцип наименьшего удивления. Используйте один подход и используйте его в своей базе кода, не смешивайте его.

nvoigt
источник
Еще одна хорошая вещь , что int f(const T x)и int f(T x)отличаются только в функции тела.
Дедупликатор
3
Функция C ++ с таким параметром, как const int xпросто чтобы прояснить, что x не изменяется внутри? Похоже, очень странный стиль для меня.
Док Браун
@DocBrown Я не оцениваю ни один из стилей, я просто говорю, что тот, кто считает, что параметры не должны быть изменены, может заставить компилятор гарантировать это, а тот, кто считает, что их изменение - это хорошо, может сделать то же самое. Обе намерения могут быть четко переданы через сам язык, и это является большим преимуществом по сравнению с языком, который не может гарантировать его в любом случае, и когда вам приходится полагаться на произвольные указания и надеяться, что все их прочитают и будут им соответствовать.
nvoigt
@nvoigt: если кто-то предпочитает изменить параметр функции («по значению»), он просто удалит constиз списка параметров, если это ему мешает. Поэтому я не думаю, что это отвечает на вопрос.
Док Браун
@DocBrown Тогда это уже не константа. Неважно, является ли это постоянным или нет, важно то, что в языке есть способ сообщить об этом разработчику без сомнения. Мой ответ: это не имеет значения, если вы четко сообщаете , что C ++ упрощает, другим нет, и вам нужны соглашения.
nvoigt
1

Да, это «религиозный конфликт», за исключением того, что Бог не читает программы (насколько я знаю), поэтому он понижается до конфликта читабельности, который становится вопросом личного суждения. И я, конечно, сделал это, и видел, как это было сделано, в обоих направлениях. Например,

распространение пустоты (объект OBJECT *, double t0, double dt) {
  двойной t = t0; / * локальная копия t0, чтобы избежать изменения ее значения * /
  пока (что угодно) {
    timestep_the_object (...);
    t + = dt; }
  возвращение; }

Так что здесь, просто из-за имени аргумента t0 , я бы предпочел не изменять его значение. Но предположим, что вместо этого мы просто изменили имя этого аргумента на tNow или что-то в этом роде. Тогда я бы, скорее всего, забыл о локальной копии и просто написал tNow + = dt; для этого последнего заявления.

Но наоборот, предположим, я знал, что клиент, для которого была написана программа, собирается нанять новых программистов с очень небольшим опытом, которые будут читать и работать над этим кодом. Тогда я бы, вероятно, беспокоился о том, чтобы спутать их с передачей по значению в сравнении со ссылкой, в то время как их умы на 100% заняты попыткой переварить всю бизнес-логику. Так что в этом случае я всегда объявляю локальную копию любого аргумента, который будет изменен, независимо от того, является ли он более или менее читаемым для меня.

Ответы на эти вопросы стиля возникают с опытом и редко имеют канонический религиозный ответ. Любой, кто думает, что есть один-единственный ответ (и он знает это :), вероятно, нуждается в некотором опыте.

Джон Форкош
источник