Подтвердить что ты не робот

Какие ошибки сохраняются по краткости?

"С++ Primer" (5-е издание) предлагает на стр. 149, что короткий код может быть менее подвержен ошибкам, чем более длинная альтернатива. В качестве примера приведена следующая строка кода:

cout << *iter++ << endl;

Он утверждает, что указанная выше строка менее подвержена ошибкам, чем альтернатива:

cout << *iter << endl;
++iter;

Для меня, хотя смысл *iter++ ясен, это выражение по-прежнему требует ненужного умственного усилия для синтаксического анализа, поэтому вторая версия более читаема. Итак, я хочу понять: что более подвержено ошибкам относительно второй версии?

4b9b3361

Ответ 1

Я также не согласен с автором.

Обычно каждый фрагмент кода имеет тенденцию к изменению в будущем.

Например, это утверждение

cout << *iter++ << endl;

можно изменить следующим образом:

cout << *iter++ << ... << SomeFunction( *iter ) << endl;

В этом случае код будет иметь поведение undefined, потому что порядок оценки аргументов функции не указан.

Итак, на мой взгляд, этот фрагмент кода

cout << *iter << endl;
++iter;

менее подвержен ошибкам.:)

Представление побочных эффектов в фрагменте кода не делает его менее подверженным ошибкам.

Кроме того, в фрагменте кода, который вы показали, нет логической связи между приращением итератора и выводом его значения.

cout << *iter++ << endl;

Так что непонятно, почему итератор увеличивается в этом выражении.

Разделение этого выражения на два утверждения делает фрагмент кода более понятным, поскольку кажется, что приращение связано с некоторыми другими частями кода.

Краткость делает код более выразительным. Но это не значит, что это обязательно делает код менее подверженным ошибкам.:)

Ответ 2

Дело в том, что когда у вас есть функция, содержащая несколько строк кода, каждый из них рассматривается как "шаг" для достижения определенной цели. Когда вы читаете такую ​​функцию, вы читаете каждую строку и думаете, что она делает и почему. В вашем примере

cout << *iter << endl;
++iter;

логически может быть одним шагом: "при итерации по контейнеру напишите каждый элемент на cout". Это потому, что, когда вы забыли одну из этих строк, весь шаг неверен. Конечно, этот пример не особенно велик, потому что нетрудно придумать код, в котором две строки представляют собой два разных логических шага. Однако я предполагаю, что автор имел в виду, что написав

cout << *iter++ << endl;

вы защищаете себя от забывания одной части логического шага, а также вы делаете сигнал для читателя, что это один логический шаг.

Ответ 3

Я могу подумать о том, что строки по какой-то причине переупорядочиваются (возможно, они были отделены каким-то другим кодом, и позже они были изменены) или добавление if/for/while, либо без брекетов, либо с неправильным размещением:

++iter;
cout << *iter << endl;

if (some_condition)
cout << *iter << endl;
++iter;

while (something_happens)
{
cout << *iter << endl;
}
++iter;

В этом маленьком примере ошибки довольно очевидны, но это может быть не так, когда у вас гораздо больше строк.

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

Ответ 4

Я больше не мог не согласиться с автором.

Выражение *iter++ делает две разные вещи в iter - оно разыгрывает и увеличивает его. Да, порядок определен, но понимание результатов двух действий по одной переменной в одном выражении требует изначально большей мощности, чем то, что оказывает только один эффект на одну переменную.

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

Достоинство разложения эффектов на два разных утверждения состоит в том, что его легче понять.

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

Ответ 5

Возможно, стоит сравнить с аналогичными конструкциями на других языках.

В python, чтобы перебрать последовательность, мы используем генератор. Существует только одна операция, которую вы можете сделать с генератором: вызов next, который получает элемент и продвигает генератор. Итерация, когда выполняется вручную, выполняется путем повторного вызова next, чтобы получить термины последовательности до тех пор, пока она не повысит значение StopIteration, чтобы сигнализировать о завершении последовательности.

В java для итерации по последовательности мы имеем Iterator. Существует по существу только одна операция, которую вы можете сделать с помощью итератора: call next, который получает элемент и продвигает итератор. Итерация, когда выполняется вручную, выполняется путем повторного вызова next, чтобы получить условия последовательности до тех пор, пока hasNext не вернет false, чтобы сигнализировать о завершении последовательности.

В C/С++... мы часто хотим получить элемент и продвигаться по последовательности. Уже давно установлено (AFAIK до того, как С++ даже существовал), что эта операция *p++.

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

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

Но относительно хорошо установлено, что это не то, как люди думают - люди думают с точки зрения "получения элемента и продвижения итератора". Итерация, когда выполняется вручную, выполняется путем многократного вызова *iter++ для получения условий последовательности, пока iter == end_iter не вернет значение true, чтобы обозначить конец последовательности.

Если вы думаете об этом, разделение одного концептуального шага на два синтаксически отдельных элемента (*iter и ++iter) больше подвержено ошибкам, чем сохранение его как одного шага.

Ответ 6

Не является хорошим примером, потому что вы не должны использовать пока без фигурных скобок, но это может привести к следующим ошибкам: в этом случае у вас будет бесконечный цикл при добавлении while.

while (*iter)
  cout << *iter << endl;
  ++iter;

Я думаю, что это плохой пример! Гораздо лучше будет пример, когда у вас есть несколько зависимых шагов. Затем кто-то копирует некоторые шаги в другую функцию, но пропускает некоторые строки, которые не выглядят семантически необходимыми.