Чи є завдання в умовній частині умовними - це погана практика?


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
Читання було дуже різним ще за часів теледрукарів та редакторів, орієнтованих на лінійку. MUSHing всі ці речі на одній лінії , використовуваної , щоб бути більш читабельним.
user2357112 підтримує Моніку

15
Я вражений тим, що вони мають індекси та порівняння до '\ 0' замість чогось подібного while (*s++ = *t++); (Мій C дуже іржавий, чи потрібні мені паролі там, де пріоритет оператора?) Чи K&R випустив нову версію своєї книги? Їх оригінальна книга мала надзвичайно стислий та ідіоматичний код.
user949300

4
Читання - дуже особиста річ - навіть за часів телетипів. Різні люди віддають перевагу різним стилям. Дуже багато інструкцій, які набирали зусилля, стосувалися генерації коду. У ті часи деякі набори інструкцій (наприклад, Загальні дані) могли поєднати кілька операцій в одну інструкцію. Також на початку 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, другу версію може бути легше зрозуміти: для них не менш зрозуміло, що робить код, для них це залишає менше питань, чому він написаний таким, яким він є, і їм , менше вертикального простору означає, що на екрані може бути відображено більше контексту.

Вам доведеться покладатися на власний здоровий глузд. Якій аудиторії ви хочете зробити свій код найпростішим для розуміння? Чи написаний цей код для компанії? Тоді цільова аудиторія - це, мабуть, інші програмісти цієї компанії. Це особистий проект хобі, над яким не буде працювати ніхто, крім вас самих? Тоді ви - власна цільова аудиторія. Це код, яким ви хочете поділитися з іншими? Тоді ці інші - ваша цільова аудиторія. Виберіть версію, яка відповідає цій аудиторії. На жаль, не існує єдиного бажаного способу заохочення.


14

EDIT: рядок s[i] = '\0';було додано до першої версії, таким чином виправивши її, як описано у варіанті 1 нижче, тому це більше не стосується поточної версії коду питання.

Друга версія має відмітною перевагою коректності , а перша - ні, вона не скасовує нульовий рядок правильно.

"Призначення в умові" дозволяє висловити поняття "скопіювати кожен символ перед тим, як перевірити нульовий символ" дуже стисло та таким чином, що оптимізація для компілятора стає дещо простішою, хоча багато інженерів програмного забезпечення в цей час вважають цей стиль коду менш читабельним . Якщо ви наполягаєте на використанні першої версії, вам доведеться виконати будь-яку

  1. додайте null-завершення після закінчення другого циклу (додавши більше коду, але ви можете стверджувати, що readabiliy робить його вартим) або
  2. змінити тіло циклу на "спочатку призначити, потім або перевірити, або зберегти призначений знак, а потім індекси приросту". Перевірка стану в середині петлі означає виривання з циклу (зменшення чіткості, нахмурене більшості пуристів). Збереження призначеного знака означатиме введення тимчасової змінної (зниження чіткості та ефективності). На моє переконання, обидва вони знищили б перевагу.

Правильно краще, ніж читабельно та стисло.
user949300

5

Відповіді Тулана Кордова та hvd досить добре висвітлюють аспекти чіткості та читаності. Дозвольте мені навести обстеження як ще одну причину на користь виконання завдань в умовах. Змінна, оголошена в умові, доступна лише в області цього твердження. Ви не можете використовувати цю змінну згодом випадково. Цикл for робить це вже віками. І досить важливо, щоб майбутні C ++ 17 вводили аналогічний синтаксис для if і switch :

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

foo = 23;   // compiler error: foo is not in scope

3

Ні. Це дуже стандартний і звичайний стиль С. Ваш приклад поганий, тому що він повинен бути просто циклом, але в цілому немає нічого поганого

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

наприклад (або з тим часом).


7
У цьому щось не так; `! = NULL` та його спорідненість в умовному режимі C - це питтер, просто там, щоб розмістити розробників, яким не зручно поняття про значення як істинне чи неправдиве (або навпаки).
Джонатан У ролях

1
@jcast Ні, це більш чітко включати != NULL.
Майлз Рут

1
Ні, сказати більш чітко (x != NULL) != 0. Зрештою, це C справді перевіряє, правда?
Джонатан У ролях

@jcast Ні, це не так. Перевірка того, чи є щось нерівне хибному, - це не те, як ви пишете умовні умови будь-якою мовою.
Майлз Рут

"Перевірка того, чи є щось нерівне з помилковим, - це не те, як ви пишете умовні умови будь-якою мовою." Саме так.
Джонатан У ролях

2

За часів K&R

  • "C" - це портативний код складання
  • Його використовували програмісти, які продумували код складання
  • Компілятор не робив великої оптимізації
  • Більшість комп'ютерів мали "складні набори інструкцій", наприклад, while ((s[i++]=t[j++]) != '\0')було б відображено одну інструкцію на більшості процесорів (я очікую, що Dec VAC)

Там дні

  • Більшість людей, які читають код C, не є програмістами програмного коду
  • C компілятори роблять багато оптимізації, отже, простіший код для читання, швидше за все, буде переведений в той же машинний код.

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

Однак за старих часів читалася б друга версія коду. (Якби я це правильно зрозумів!)

concat(char* s, char *t){      
    while (*s++);
    --s;
    while (*s++=*t++);
}

2

Навіть вміти це зробити взагалі - дуже погана ідея. Це розмовно відоме як "Остання помилка у світі", наприклад:

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

Хоча ви, швидше за все, не помилитесь, що є настільки серйозною помилкою , дуже легко випадково викрутитись і викликати важко знайти помилку у вашій базі коду. Більшість сучасних компіляторів вставить попередження про завдання всередині умовного. Вони там є чомусь, і ви б добре прислухалися до них і просто уникали цієї конструкції.


Перед цим попередженням ми б писали CODE_RED = alertтак, що воно дало помилку компілятора.
Ян

4
@Ian Yoda Conditionals, що називається. Важко їх читати. Невдача необхідність для них є.
Мейсон Уілер

Після дуже короткого вступного періоду "звикання до нього", умови Йоди читати не важче, ніж звичайні. Іноді вони легше читаються . Наприклад, якщо у вас є послідовність ifs / elseifs, то умова, яку перевіряють зліва на вищий акцент, є незначним поліпшенням IMO.
user949300

2
@ user949300 Два слова: Стокгольмський синдром: P
Мейсон Уілер

2

Обидва стилі добре сформовані, правильні та доречні. Який із них є більш підходящим, багато в чому залежить від настанов вашої компанії. Сучасні IDE полегшать використання обох стилів за допомогою використання живого синтаксису, який чітко виділяє області, які в іншому випадку можуть стати джерелом плутанини.

Наприклад, Netbeans виділяє таке вираження :

if($a = someFunction())

на підставі "випадкового призначення".

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

Щоб чітко сказати Netbeans, що "так, я дійсно мав намір це зробити ...", вираз можна загорнути в набір дужок.

if(($a = someFunction()))

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

Зрештою, все це зводиться до керівних принципів компанії та наявності сучасних інструментів для полегшення процесу розробки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.