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

В чем преимущества раздачи задания и проверки ошибок в одной строке?

Этот вопрос вдохновлен этим вопросом, в котором представлен следующий фрагмент кода.

int s;
if((s = foo()) == ERROR)
    print_error();

Я считаю, что этот стиль трудно читать и подвергать ошибкам (как показывает оригинальный вопрос - это было вызвано отсутствием скобок вокруг назначения). Вместо этого я напишу следующее, что на самом деле короче с точки зрения символов.

int s = foo();
if(s == ERROR)
    print_error();

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

4b9b3361

Ответ 1

Когда вы пишете цикл, иногда желательно использовать первую форму, как в этом знаменитом примере из K & R:

int c;

while ((c = getchar()) != EOF) {
    /* stuff */
}

Нет никакого изящного способа "второй формы" написания этого без повторения:

int c = getchar();

while (c != EOF) {
    /* stuff */
    c = getchar();
}

Или:

int c;

for (c = getchar(); c != EOF; c = getchar()) {
    /* stuff */
}

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

Таким образом, нужно научиться легко читать и писать первую форму. И учитывая, что логично использовать ту же форму в if условиях.

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

Ответ 2

Я думаю, что это по истерическим причинам, что ранние компиляторы не были настолько умны в оптимизации. Помещая его на одну строку как одно выражение, он дает компилятору намек на то, что одно и то же значение, полученное из foo(), может быть протестировано, а не специально загружать значение из s.

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

Ответ 3

Я сознательно пытаюсь объединить их, когда это возможно. "Штраф" по размеру недостаточно, чтобы преодолеть преимущество в ясности, ИМО.

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

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

Ответ 4

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

Ответ 5

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

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

YMMV для каждого компилятора и отладчика и для оптимизированных сборников, конечно.

Ответ 6

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

Это также позволяет мне более легко делать такие вещи, как:

int rc = function();

DEBUG_PRINT(rc);

if (rc == ERROR) {
    recover_from_error();
} else {
    keep_on_going(rc);
}

Я предпочитаю этот стиль настолько, что в случае циклов я предпочел бы:

while (1) {
    int rc = function();
    if (rc == ERROR) {
        break;
    }
    keep_on_going(rc);
}

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

Ответ 7

Я часто предпочитаю первую форму. Я не мог точно сказать, почему, но это имеет какое-то отношение к вовлеченной семантике.

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

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

Ответ 8

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

Разделение назначений и сравнение делают как более четкие, так и менее подверженные ошибкам, даже если дублирование сравнения может иногда вводить ошибку. Помимо всего прочего, скобки быстро усложняются, чтобы различать и удерживать все в одной строке, введя больше скобок. Кроме того, разделение на него ограничивает утверждения только тем, что он либо извлекает значение, либо назначает его.

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