Являются ли задания в условной части условными обозначениями плохой практикой?

35

Давайте предположим, что я хочу написать функцию, которая объединяет две строки в C. Я бы написал так:

void concat(char s[], char t[]){
    int i = 0;
    int j = 0;

    while (s[i] != '\0'){
        i++;
    }

    while (t[j] != '\0'){
        s[i] = t[j];
        i++;
        j++;
    }

    s[i] = '\0';

}

Тем не менее, K & R в своей книге реализовал это по-другому, особенно включив как можно больше в условную часть цикла while:

void concat(char s[], char t[]){
    int i, j;
    i = j = 0;
    while (s[i] != '\0') i++;

    while ((s[i++]=t[j++]) != '\0');

}

Какой способ предпочтительнее? Рекомендуется или не рекомендуется писать код так, как это делает K & R? Я верю, что моя версия будет легче читаться другими людьми.

Ричард Смит
источник
38
Не забывайте, что K & R была впервые опубликована в 1978 году. С тех пор в нашем коде произошли небольшие изменения.
CorsiKa
28
Во времена телепринтеров и линейных редакторов читаемость была совсем другой. Помещение всего этого в одну строку раньше было более читабельным.
user2357112 поддерживает Monica
15
Я шокирован, что у них есть индексы и сравнения с '\ 0' вместо чего-то вроде while (*s++ = *t++); (Мой C очень ржавый, нужны ли там парены для старшинства операторов?) Выпустила ли K & R новую версию своей книги? Их оригинальная книга имела чрезвычайно лаконичный и идиоматический код.
user949300
4
Читаемость - вещь очень личная, даже во времена телетайпа. Разные люди предпочитают разные стили. Большая часть инструкций была связана с генерацией кода. В те дни некоторые наборы команд (например, Data General) могли объединять несколько операций в одну инструкцию. Кроме того, в начале 80-х существовал миф о том, что использование скобок приводит к появлению большего количества инструкций. Мне пришлось сгенерировать ассемблер, чтобы доказать рецензенту, что это миф.
Кубок
10
Обратите внимание, что два кодовых блока не эквивалентны. Первый блок кода не будет копировать завершающий '\0'изt (на whileвыходах первого). Это оставит полученную sстроку без окончания '\0'(если только ячейка памяти не была обнулена). Второй блок кода создаст копию завершения '\0'до выхода из whileцикла.
Макьен

Ответы:

80

Всегда предпочитайте ясность над умом. В прошлые годы лучшим программистом был тот, чей код никто не мог понять. «Я не могу понять его код, он должен быть гением» , - сказали они. На сегодняшний день лучшим программистом является тот, чей код может понять каждый. Компьютерное время теперь дешевле, чем время программиста.

Любой дурак может написать код, понятный компьютеру. Хорошие программисты пишут код, который могут понять люди. (М. Фаулер)

Так что, без сомнения, я бы выбрал вариант А. И это мой окончательный ответ.

Тулаинс Кордова
источник
8
Очень содержательная идеология, но факт остается фактом: в условных заданиях нет ничего плохого. Гораздо предпочтительнее ранний выход из цикла или дублирование кода до и внутри цикла.
Майлз Рут
26
@MilesRout есть. Что-то не так с любым кодом, имеющим побочный эффект там, где вы этого не ожидаете, т.е. передачей аргументов функции или вычислением условий. Даже не упоминая, что if (a=b)можно легко принять за if (a==b).
Артур Гавличек
12
@Luke: «Моя IDE может очистить X, поэтому это не проблема», довольно неубедительно. Если это не проблема, почему IDE так легко "исправить"?
Кевин
6
@ArthurHavlicek Я согласен с вашей общей точкой зрения, но код с побочными эффектами в условных выражениях действительно не так уж редок: while ((c = fgetc(file)) != EOF)как первое, что приходит мне в голову.
Даниэль Жур
3
+1 "Учитывая, что отладка в два раза сложнее, чем писать программу в первую очередь, если вы будете настолько умны, насколько это возможно, когда будете ее писать, как вы будете когда-либо отлаживать ее?" BWKernighan
Кристоф
32

Золотое правило, как и в ответе Тулаинса Кордовы, - это писать понятный код. Но я не согласен с выводом. Это золотое правило означает написание кода, который может понять обычный программист, который в конечном итоге будет поддерживать ваш код. И вы лучший судья в том, кто типичный программист, который в конечном итоге будет поддерживать ваш код.

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

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

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

HVD
источник
14

РЕДАКТИРОВАТЬ: линияs[i] = '\0'; была добавлена ​​в первую версию, тем самым исправляя ее, как описано в варианте 1 ниже, так что это больше не относится к текущей версии кода вопроса.

Вторая версия имеет отличительное преимущество в том, что она правильная , в то время как первая - нет - она ​​неправильно завершает целевую строку.

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

  1. добавить нулевое завершение после окончания второго цикла (добавление большего количества кода, но вы можете утверждать, что readabiliy делает его стоящим) или
  2. измените тело цикла на «сначала назначить, затем либо проверить, либо сохранить назначенный символ, а затем увеличить индексы». Проверка состояния в середине цикла означает разрыв цикла (снижение ясности, вызывающее недовольство большинства пуристов). Сохранение назначенного символа будет означать введение временной переменной (снижение ясности и эффективности). Оба из них уничтожили бы преимущество по моему мнению.
hjhill
источник
Правильный лучше, чем читаемый и краткий.
user949300
5

Ответы Tulains Córdova и hvd достаточно хорошо охватывают аспекты ясности и читабельности. Позвольте мне добавить обзор в качестве еще одной причины в пользу назначения в условиях. Переменная, объявленная в условии, доступна только в области действия этого оператора. Вы не можете использовать эту переменную впоследствии случайно. Цикл for делал это целую вечность. И достаточно важно, чтобы в следующем C ++ 17 был введен похожий синтаксис для if и switch :

if (int foo = bar(); foo > 42) {
    do_stuff();
}

foo = 23;   // compiler error: foo is not in scope
BESC
источник
3

Нет. Это очень стандартный и нормальный стиль C. Ваш пример плохой, потому что он должен быть просто циклом for, но в целом нет ничего плохого

if ((a = f()) != NULL)
    ...

например (или с помощью while).

Майлз Рут
источник
7
Что-то не так с этим; `! = NULL` и его род в условном Си более естественны, просто для того, чтобы успокоить разработчиков, которые не удовлетворены концепцией истинного или ложного значения (или наоборот).
Джонатан Каст
1
@jcast Нет, это более явно включить != NULL.
Майлз Рут
1
Нет, это более ясно, чтобы сказать (x != NULL) != 0. В конце концов, это то, что C действительно проверяет, верно?
Джонатан В ролях
@jcast Нет, это не так. Проверка того, является ли что-то неравным ложному, - это не то, как вы пишете условные выражения на каком-либо языке.
Майлз Рут
«Проверять, является ли что-то неравным ложному, - это не то, как вы пишете условные выражения на каком-либо языке». В точку.
Джонатан В ролях
2

Во времена K & R

  • 'C' был переносным кодом сборки
  • Он использовался программистами, которые думали в ассемблерном коде
  • Компилятор не делал много оптимизации
  • Большинство компьютеров имели «сложные наборы команд», например while ((s[i++]=t[j++]) != '\0'), отображались бы на одну инструкцию на большинстве процессоров (я думаю, декабрьский VAC)

Там дни

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

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

Однако в старые времена 2-я версия кода читалась. (Если я правильно понял!)

concat(char* s, char *t){      
    while (*s++);
    --s;
    while (*s++=*t++);
}
Ян
источник
2

Даже быть способным сделать это вообще очень плохая идея. В разговорной речи это называется «Последняя ошибка в мире», например:

if (alert = CODE_RED)
{
   launch_nukes();
}

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

Мейсон Уилер
источник
Перед этим предупреждением мы написали бы CODE_RED = alertтак, чтобы это дало ошибку компилятора.
Ян
4
@Ian Yoda Условные, что называется. Трудно читать, они есть. К сожалению, необходимость в них есть.
Мейсон Уилер
После очень короткого вводного периода «привыкания к нему» условия Йоды не сложнее, чем обычные. Иногда они более читабельны . Например, если у вас есть последовательность ifs / elseifs, наличие условия, проверяемого слева, для более сильного акцента - это небольшое улучшение IMO.
user949300
2
@ user949300 Два слова: Стокгольмский синдром: P
Мейсон Уилер
2

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

Например, Netbeans выделяет следующее выражение :

if($a = someFunction())

на основании "случайного назначения".

введите описание изображения здесь

Чтобы явно сказать Netbeans, что «да, я действительно хотел это сделать ...», выражение можно заключить в скобки.

if(($a = someFunction()))

введите описание изображения здесь

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

Люк А. Лебер
источник