2015-07-01 2 views
25

«C++ Primer» (5-е издание) предлагает на странице 149, что короткий код может быть менее подвержен ошибкам, чем более длинная альтернатива. Она приносит следующую строку кода, чтобы служить в качестве примера:Какие ошибки сохраняются по краткости?

cout << *iter++ << endl; 

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

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

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

+31

Кто-то курил что-то, когда это было написано. Ясно, что 2-й недвусмыслен. – leppie

+2

Я думаю, что больше кода. Написание большего количества кода приводит к большей склонности к ошибкам. –

+0

Я не вижу этот код в книге. Вы уверены в странице? Какая глава/параграф? – Brahim

ответ

4

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

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

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

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

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

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

+0

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

+1

Я никогда не говорил иначе :-) Я просто попытался ответить на вопрос: «Что более подвержено ошибкам в отношении второй версии?», Тем самым перечисляя возможности. –

21

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

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

Например, это заявление

cout << *iter++ << endl; 

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

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

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

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

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

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

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

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

cout << *iter++ << endl; 

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

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

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

+1

Имеет смысл, если вы читаете '* iter ++' как «следующее значение из iter» - вместо того, чтобы разделить его на две части «текущее значение от iter» и «advance iter». («Следующий» означает первый, который еще не обработан, а не тот, который после текущей позиции) – immibis

+0

@immibis Обычно в том случае, когда тело замкнутого цикла состоит из одного оператора или когда это утверждение последнее утверждение в теле. Тем не менее в любом случае ничто не мешает разбить это утверждение на два утверждения. И код будет еще более читабельным, потому что вам не нужно будет расследовать это утверждение, чтобы быть уверенным, что вы не забыли увеличить итератор. :) –

+0

Почему вы не проголосовали за закрытие? Если ответ начинается с «Я не согласен с [другим ответом]», вопрос явно просят мнения ... –

3

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

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

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

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

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

+0

Я думаю, что точка, которую пытается попытаться автор, проиллюстрирована в ответе @ JavierNoval. – Brian

+0

В примере и рассуждениях книги люди склонны полагать, что если этот пример «хорош», то аналогично «Write (* iter ++)», это хорошо. Тогда 'Write2 (* iter ++, * iter ++);' также «хорошо». Но этот третий пример очень плох. – franji1

+0

@staticx: Тот факт, что я не согласен с тем, что делает автор, не означает, что я не понимаю его. – Peter

5

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

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

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

cout << *iter++ << endl; 

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

+3

+1. Если это то, что подразумевали авторы «C++ Primer», то это слишком плохо, что они не говорили об этом. Это было бы важным уроком для читателя. – AlwaysLearning

+1

Из книги, ссылающейся на этот фрагмент кода: «Это стоит изучить примеры такого кода до тех пор, пока их значения не будут немедленно очистят. Большинство программ на C++ используют сжатые выражения, а не более подробные эквиваленты. Поэтому программисты на C++ должны быть удобными с такими обычаями. Более того, как только эти выражения будут знакомы, вы найдете менее подверженными ошибкам ». – Brian

+3

Это в основном означает «Напиши плохой код, чтобы понять плохой код» – inf

2

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

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

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

+0

Возможно, вы захотите добавить фигурные скобки, потому что, поскольку он стоит, вы застряли в бесконечном цикле. Точка с запятой подразумевает, что вы не используете Python. – leppie

+1

@lebbie, это было своего рода точкой этого ответа. Сокращение спасет вас, если весь код находится на одной линии, вы даже без брекетов. - Но это слабый момент, так как вы всегда должны использовать фигурные скобки. Но это то же самое с копией и вставкой, с несколькими линиями, которые вы могли бы пропустить. – Falco

+0

Извините :) Мой плохой, я не читал ваш ответ должным образом. – leppie

3

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

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

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

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

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

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

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

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

Смежные вопросы