Чому висловлювання "if elif else" практично ніколи не бувають у форматі таблиці?


73
if   i>0 : return sqrt(i)  
elif i==0: return 0  
else     : return 1j * sqrt(-i)

VS

if i>0:  
   return sqrt(i)  
elif i==0:  
   return 0  
else:  
   return 1j * sqrt(-i)  

З огляду на наведені вище приклади, я не розумію, чому я практично ніколи не бачу першого стилю в кодових базах. Для мене ви перетворюєте код у табличний формат, який чітко показує, що ви хочете. Перший стовпець можна практично ігнорувати. Другий стовпець ідентифікує умову, а третій стовпець дає вам потрібний результат. Мені здається, принаймні мені, прямолінійний і легкий для читання. Але я завжди бачу, як ця проста ситуація з випадком / вимикачем виходить у розширеному форматі з відступом на вкладці. Чому так? Чи вважають люди другий формат більш читабельним?

Єдиний випадок, коли це може бути проблематичним, це якщо код змінюється і стає довшим. У цьому випадку я вважаю, що цілком розумно переробити код у довгий, відступний формат. Чи всі роблять це по-другому просто тому, що це було так, як це робилося завжди? Будучи захисником диявола, я думаю, що ще одна причина може полягати в тому, що люди знаходять два різні формати залежно від складності тверджень if / else заплутаними? Будь-яке розуміння буде вдячне.


91
Тому що люди вважають другий варіант більш зрозумілим?
GrandmasterB

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

47
@horta "Єдиний випадок, коли це може бути проблематичним - це зміни коду та його збільшення". - НІКОЛИ не слід вважати, що фрагмент коду не буде змінено. Код рефакторингу займає значну більшість життєвих циклів програмного забезпечення.
Чарльз Аддіс

7
@horta: Нічого спільного зі мною. Це код. У контексті кодової бази, яку я читаю, я хочу перевірити, чи твердження (та інші мовні конструкції) послідовно відформатовані, без будь-яких кращих справ. Це не те, що я навчився читати код певним чином, я можу читати або просто чудово, але це читабельніше, якщо все те саме. Знову ж таки, не те саме для мене, те саме, що в решті коду.
GManNickG

44
Крім того, більшість налагоджувачів є лінійними. Неможливо поставити точку перерви на оператор, який знаходиться всередині, ifякщо він знаходиться на тій же лінії.
isanae

Відповіді:


93

Однією з причин може бути те, що ви не використовуєте мови там, де це популярно.

Кілька зустрічних прикладів:

Haskell з охоронцями та з візерунками:

sign x |  x >  0        =   1
       |  x == 0        =   0
       |  x <  0        =  -1

take  0     _           =  []
take  _     []          =  []
take  n     (x:xs)      =  x : take (n-1) xs

Ерланг з візерунками:

insert(X,Set) ->
    case lists:member(X,Set) of
        true  -> Set;
        false -> [X|Set]
    end.

Emacs lisp:

(pcase (get-return-code x)
  (`success       (message "Done!"))
  (`would-block   (message "Sorry, can't do it now"))
  (`read-only     (message "The shmliblick is read-only"))
  (`access-denied (message "You do not have the needed rights"))
  (code           (message "Unknown return code %S" code)))

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


10
Я підтримав це і, як правило, погоджуюся, але відчуваю себе зобов’язаним зазначити, що 1. Все це - тривіальні повернення 2. Haskellers люблять комічно короткі ідентифікатори на додаток до терміновості самої мови та 3. Функціональні мови, як правило, засновані на вираженні і навіть імперативні мови мають різні стандарти виразів, ніж твердження. Якщо ОП переписав оригінальний приклад як функціональні програми, він може отримати іншу пораду ...
Джаред Сміт

3
@JaredSmith Дякую за розбиття на основі висловів / висловлювань - я думаю, це може бути навіть більш придатним, ніж функціональним / імперативним. Знову ж таки, рубін майже заснований на виразі, і він не використовує цю умову часто. (винятки з усіх) Що стосується пунктів 1 і 2, я вважаю, що 50% + реального коду Haskell є "тривіальними поверненнями", які є лише частинами чогось більшого - саме так пишеться цей код - не тільки в наведених тут прикладах. Наприклад , близьких до половини функцій тут є один тільки один / два лайнери. (в деяких рядках використовується макет таблиці)
viraptor

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

Я думаю, що саме синтаксис шаблону цього випадку сприяє цьому. Оскільки це більш стисло і зазвичай ближче до короткого корпусу перемикача, його легше виразити як одноколірний. Я часто це роблю і з короткими заявами про переключення, і з подібних причин. Буквальні if-elseвисловлювання, як правило, розповсюджуються в декілька рядків, хоча фактично не є простим потрійним.
Isiah Meadows

@viraptor Технічно інші 50% коду haskell - це "нетривіальні повернення", оскільки всі функції haskell функціонально чисті і не можуть мати побічних ефектів. Навіть функції, які читаються з командного рядка та друкуються з них, - це лише тривалі оператори повернення.
Фарап

134

Це читабельніше. Кілька причин:

  • Майже кожна мова використовує цей синтаксис (не всі, більшість - ваш приклад, здається, Python)
  • isanae зазначив у коментарі, що більшість налагоджувачів на основі рядків (не на основі заяви)
  • Це починає виглядати ще більш потворно, якщо вам доведеться вставляти крапки з комою або дужки
  • Він читається зверху вниз більш плавно
  • Це виглядає жахливо нечитабельним , якщо у вас є що - небудь, крім тривіальних тверджень повернення
    • Будь-який значущий синтаксис відступу втрачається, коли ви знімаєте код, оскільки умовний код більше не візуально відокремлений (від Dan Neely )
    • Це буде особливо погано, якщо ви продовжуєте виправляти / додавати елементи в 1-й рядок, якщо заяви
  • Це читається лише у тому випадку, якщо всі ваші, якщо чеки, приблизно однакової довжини
  • Це означає, що ви не можете форматувати складні, якщо висловлювання у багаторядкові оператори, вони повинні бути однолінійними
  • Я набагато частіше помічаю помилки / логічний потік, читаючи вертикально по черзі, не намагаючись проаналізувати кілька рядків разом
  • Наші мізки читають вузький і високий текст НАШЕ швидше, ніж довгий горизонтальний текст

Щоразу, коли ви спробуєте зробити що-небудь із цього, ви в кінцевому підсумку переписуєте його у багаторядкові висловлювання. Що означає, що ви просто витратили час!

Також люди неминуче додають щось на кшталт:

if i>0:  
   print('foobar')
   return sqrt(i)  
elif i==0:  
   return 0  
else:  
   return 1j * sqrt(-i)  

Це потрібно не дуже часто, перш ніж ви вирішите, що цей формат набагато краще, ніж ваша альтернатива. Ах, але ти міг би все це вкласти в один рядок! Ендерленд гине зсередини .

Або це:

if   i>0 : return sqrt(i)  
elif i==0 and bar==0: return 0  
else     : return 1j * sqrt(-i)

Що насправді, насправді дратує. Ніхто не любить форматувати такі речі.

І останнє, ви почнете святу війну проблеми "скільки пробілів для вкладок". Те, що ідеально відображається на екрані у форматі таблиці, може не відображатись у моєму залежно від налаштувань.

Читання в будь-якому випадку не повинно залежати від налаштувань IDE.


14
@horta, тому що вам доведеться конвертувати його, якщо ви спочатку відформатуєте його? Я весь про мінімізацію роботи для майбутнього ендерленду. Створення гарних таблиць пробілів та підрахунок пробілів та вкладок для оновлення візуального форматування, коли я можу додати деяку логіку до if, якщо перевірка зовсім не задоволена (ігнорування наслідків читабельності).
enderland

14
@horta Він не обов'язково говорить, що не буде цього виправляти, він каже, що доведеться це виправити, і це багато часу витрачається на нудне форматування, а не на програмування.
Сервіс

11
@horta: IMHO ваш шлях часто менш читабельний і, безумовно, набагато більше дратує формат. Крім того, його можна використовувати лише тоді, коли "ifs" - це невеликі вкладиші, що рідко. Нарешті, IMHO якось не приємно, через це переносити два види "якщо" форматування.
dagnelies

9
@horta вам здається, що ви працюєте над системами, де вимоги, системи, API та потреби користувача ніколи не змінюються. Вам щаслива душа.
Ендерленд

11
Я додам: будь-яка невелика зміна однієї умови може зажадати переформатування інших, щоб вони відповідали :позиції -> робити різницю в CVS, раптом стає складніше зрозуміти, що насправді змінюється. Це справедливо і для стану проти організму. Якщо їх змінити на окремих лініях, це означає, що якщо змінити лише один із них, розрізнення чітко показує, що змінилася лише умова, а не тіло.
Бакуріу

55

Я твердо вірю в те, що «код читається багато разів, пишеться мало - тому читабельність дуже важлива».

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


3
Відповідно: thecodelesscode.com/case/94
Кевін

11
Це пояснює, чому люди консервативні. Це не пояснює, чому люди вирішили написати свій код певним способом.
Jørgen Fogh

8
Питання було "чому я часто бачу це", а не звідки цей стиль. Обидва питання цікаві; Я спробував відповісти на той, кого я думав, що мене запитують.
Art Swri

16

Поряд з іншими згаданими недоліками, табличне розташування збільшує шанси конфліктів злиття контролю версій, що вимагають вручну втручання.

Коли потрібно блокувати таблично впорядкований код, система управління версіями буде розглядати кожен з цих рядків як змінений:

diff --git a/foo.rb b/foo.rb
index 40f7833..694d8fe 100644
--- a/foo.rb
+++ b/foo.rb
@@ -1,8 +1,8 @@
 class Foo

   def initialize(options)
-    @cached_metadata = options[:metadata]
-    @logger          = options[:logger]
+    @metadata = options[:metadata]
+    @logger   = options[:logger]
   end

 end

Тепер припустимо, що тим часом в іншій гілці програміст додав новий рядок до блоку вирівняного коду:

diff --git a/foo.rb b/foo.rb
index 40f7833..86648cb 100644
--- a/foo.rb
+++ b/foo.rb
@@ -3,6 +3,7 @@ class Foo
   def initialize(options)
     @cached_metadata = options[:metadata]
     @logger          = options[:logger]
+    @kittens         = options[:kittens]
   end

 end

Об’єднання цієї гілки не вдасться:

wayne@mercury:/tmp/foo$ git merge add_kittens
Auto-merging foo.rb
CONFLICT (content): Merge conflict in foo.rb
Automatic merge failed; fix conflicts and then commit the result.

Якби модифікований код не використовував табличне вирівнювання, злиття було б успішним автоматично.

(Ця відповідь була "плагіатом" з моєї власної статті, що відлякує табличне вирівнювання в коді).


1
Цікаво, але хіба це невдача інструментів злиття? Конкретно git у цьому випадку? Це точка, спрямована на конвенцію, - простий шлях. Для мене це щось, що можна вдосконалити (з боку інструменту).
хорта

7
@horta Щоб інструмент об'єднання змінив простір білим способом, який майже не завжди порушує код, йому доведеться зрозуміти, якими способами він може змінювати пробіл без зміни значення коду. Також доведеться зрозуміти, яке саме табличне вирівнювання використовується. Це буде залежати не лише від мови (Python!), Але, ймовірно, знадобиться інструмент для певного розуміння коду. З іншого боку, об'єднання на основі рядків може бути здійснено без AI, і досить часто навіть не порушує код.
Уейн Конрад

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

1
@horta Правильно. Більшість моїх заперечень щодо вирівнювання таблиць у коді може знищитись за допомогою досить просунутих інструментів.
Уейн Конрад

8

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

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


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

6

Є твердження "перемикач", яке забезпечує подібні речі для особливих випадків, але я думаю, що це не те, про що ви питаєте.

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

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


1
ОП, здається, використовує Python, тому ні switch.
Джаред Сміт

2
"3, якщо висловлювання найкраще відображаються у традиційному форматі, але якщо у вас було 20" ... тоді у вас є більші проблеми, над якими можна задуматися! :)
Грімм Opiner

@GrimmTheOpiner Якщо ви пишете мовний аналізатор або стрипіфікатор AST, це дуже можлива справа. Наприклад, я одного разу зробив внесок у аналізатор JavaScript, де я розділив функцію на 15-20 випадків, по одному для кожного типу виразів. Я сегментував більшість випадків на власні функції (для помітного прискорення парфумів), але довгий switchбув необхідністю.
Isiah Meadows

@JaredSmith: Мабуть, switchце зло, але інстанціювання словника, а потім проведення пошуку на ньому, щоб зробити тривіальне розгалуження, не є злом ...
Марк К Коуан

1
@MarkKCowan о, я зловив сарказм, але подумав, що ти використовуєш його, щоб знущатися зі мене. відсутність контексту в Інтернеті і нічого такого.
Джаред Сміт

1

Якщо ваше вираження справді таке просто, більшість мов програмування пропонують оператор розгалуження ?:

return  ( i > 0  ) ? sqrt( i)
      : ( i == 0 ) ? 0
        /* else */ : 1j * sqrt( -i )

Це короткий читаний табличний формат. Але важлива частина полягає в тому, що я бачу одним поглядом, що таке "основна" дія. Це повернення! І значення визначається певними умовами.

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

if ( i > 0 )
{
    throw new InvalidInputException(...);
}
else if ( i == 0 )
{
    return 0;
}
else
{
    log( "Calculating sqrt" );
    return sqrt( -i );
}

7
Насправді, я вважаю ваш "короткий читабельний табличний формат" цілком кошмаром для читання, в той час як формат, запропонований ОП, цілком чудовий.
Matteo Italia

@MatteoItalia як щодо цієї відредагованої версії?
Falco

5
Вибачте, ще гірше; Я думаю, що це пов'язано з тим, що ?і :їх важче помітити, ніж if/ elseключові слова та / або через доданий "шум" символів.
Matteo Italia

@MatteoItalia: У мене були випадки з понад сотнями різних значень. Значення таблиці дозволяє перевірити наявність помилок. З багаторядковою неможливо.
gnasher729

1
@ gnasher729 - Для 100 різних "значень" я, як правило, набагато краще оголосити структуру даних кожного "елемента" і перерахувати все це як таблицю при ініціалізації для масиву цих структур даних. (Звичайно, тут можуть застосовуватися обмеження мови). Якщо будь-який елемент вимагає "обчислювального" аспекту, структура елемента може містити вказівник або посилання на функцію для виконання необхідної дії. Для програм у травні це може значно спростити код і значно полегшити обслуговування.
Майкл Карась

1

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

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

Однією популярною помилкою, до якої часто звертаються стандарти кодування, є ця маленька дітка: -

if (x == 10)
    do_something();
    do_something_else();

Це не робить те, що ви думаєте, що це робить. Що стосується C, якщо x дорівнює 10, то ви дзвоните do_something(), але потім do_something_else()отримуєте виклик незалежно від значення x . Умови лише дії, що безпосередньо йде за твердженням "якщо". Це може бути те, що кодер задумав, і в цьому випадку існує потенційна пастка для обслуговуючого персоналу; або це може бути не те, що кодер задумав, і в цьому випадку помилка. Це популярне запитання про інтерв'ю.

Рішення в стандартах кодування полягає в призначенні дужок навколо всіх умовних дій, навіть якщо вони однолінійні. Зараз ми отримуємо

if (x == 10)
{
    do_something();
    do_something_else();
}

або

if (x == 10)
{
    do_something();
}
do_something_else();

і тепер це працює правильно і зрозуміло для обслуговуючого персоналу.

Ви помітите, що це цілком несумісне з вашим форматом таблиці.

Деякі інші мови (наприклад, Python) розглядали цю проблему і вирішили, що оскільки кодери використовують пробіли, щоб зробити макет зрозумілим, було б корисно використовувати пробіл замість дужок. Так у Python,

if x == 10:
    do_something()
    do_something_else()

робить дзвінки і на, do_something()і на do_something_else()умову x == 10, тоді як

if x == 10:
    do_something()
do_something_else()

означає, що лише do_something()умовна х, і do_something_else()завжди називається.

Це дійсна концепція, і ви знайдете кілька мов, якими користуєтесь. (Я вперше побачив це в Occam2, назад, коли.) Однак, знову ж таки, ви можете легко побачити, що ваш формат таблиці не сумісний з мовою.


1
Я думаю, ти пропустив справу. Питання, про яке ви говорите, - це дивний специфічний нестандартний кошмар для С, який викликає проблему, про яку ви говорите. Якщо кодування в C, я ніколи не рекомендував би використовувати альтернативний простий метод if, якщо я запропонував табличний формат. Натомість, оскільки ви використовуєте C, ви використовуєте дужки всі в одному рядку. Фігурні дужки фактично зробили формат таблиці ще більш зрозумілим, оскільки вони виступають як роздільники.
хорта

Також зворотні заяви в цьому випадку є лише прикладом. Взагалі, це може бути кодовим запахом. Я просто маю на увазі формат простих висловлювань, зовсім не обов'язково із поверненнями.
хорта

2
Моя думка полягала в тому, що це робить ще більш незграбним формат таблиці. Між іншим, це не специфічно для C - його поділяють усі мови, похідні від C, тому C ++, C #, Java та JavaScript дозволяють отримувати однакові gotcha.
Грем

1
Я не заперечую, що це зворотні заяви - я розумію, що ваш намір полягає в тому, щоб показати прості заяви. Але воно стає більш громіздким. І звичайно, як тільки будь-який оператор стає не простим, тоді вам потрібно змінити форматування, оскільки формат таблиці підтримувати неможливо. Якщо ви не робите припухлість коду, довгі рядки коду є самим запахом. (Оригінальний ліміт становив 80 знаків; в наші дні це, як правило, близько 130 символів, але загальний принцип все ще дотримується того, що вам не доведеться прокручувати, щоб побачити кінець рядка.)
Грем

1

Табличний макет може бути корисним у кількох обмежених випадках, але в декількох випадках це корисно, якщо.

У простих випадках?: Може бути кращим вибором. У середніх випадках перемикач часто краще підходить (якщо у вас є мова). У складних випадках ви можете виявити, що таблиці дзвінків краще підходять.

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

Було кілька запитань ?:. Так, це потрійний оператор (або як мені подобається вважати це значення, якщо). на перших рум'янах цей приклад трохи складний для?: (і перестарається?: не допомагає читати, але шкодить), але з деякою думкою. Приклад можна переставити як нижче, але я думаю, що в цьому випадку перемикач є найбільш читабельним рішення.

if i==0: return 0
return i>0?sqrt(i):(1j*sqrt(-i))

1
Можливо, вам доведеться уточнити, що таке "?:" Для непосвячених (наприклад, із прикладом, можливо, пов'язаним із питанням).
Пітер Мортенсен

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

@PeterMortensen: Якщо незнайомі не знають, що це означає, вони повинні триматися подалі від коду, поки не зададуть очевидне питання і не дізнаються.
gnasher729

@horta Ternary є if ? do stuff : do other stuff. Той самий порядок, як і якщо / ще.
Навін

1
@Navin Ах, можливо, це просто збій мови, якою я користуюсь найбільше (python). stackoverflow.com/questions/394809/…
хорта

-3

Я не бачу нічого поганого у форматі таблиці. Особисті переваги, але я б користувався таким потрійником:

return i>0  ? sqrt(i)       :
       i==0 ? 0             :
              1j * sqrt(-i)

Не потрібно повторювати returnкожен раз :)


1
Як вже згадувалося в кількох коментарях, зворотні заяви не є ідеальними або суть пункту повідомлення, просто шматок коду, який я знайшов в Інтернеті та відформатував декількома способами.
хорта

Питонські тернари є do_something() if condition() else do_something_else(), ні condition() ? do_something() : do_something_else().
Isiah Meadows

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