Почему операторы if if else else практически никогда не представлены в табличном формате?

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

В.С.

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той же строки.
Исана

Ответы:

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)))

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

viraptor
источник
10
Я проголосовал за это и в целом согласен, но чувствую себя обязанным указать, что 1. Все это тривиальные результаты 2. Хаскеллерам нравятся комично короткие идентификаторы в дополнение к краткости самого языка и 3. Функциональные языки, как правило, основаны на выражениях и даже императивные языки имеют другие стандарты для выражений, чем утверждения. Если ОП переписал исходный пример как приложение функций, он мог бы получить другой совет ...
Джаред Смит
3
@JaredSmith Спасибо за разделение на основе выражений / операторов - я думаю, что оно может быть даже более подходящим, чем функциональное / императивное. Кроме того, ruby ​​почти основан на выражениях и редко использует это соглашение. (исключения из всего) Что касается пунктов 1 и 2, я считаю, что 50% + реального кода на Haskell являются «тривиальными возвратами», которые являются просто частями чего-то большего - это просто то, как этот код написан - не только в примерах здесь. Например, почти половина функций здесь - это один-два вкладыша. (некоторые строки используют макет таблицы)
viraptor
Да. Я не Haskeller, но сделаю несколько часов и обнаружу, что сопоставление с образцом имеет тенденцию быть намного более кратким, чем логически эквивалентные переключатели, и полиморфные функции в любом случае покрывают большую часть этого основания. Я полагаю, что классы типов Хаскелла еще больше расширили бы этот охват.
Джаред Смит
Я думаю, что это способствует синтаксису шаблонного случая. Поскольку он более лаконичен и, как правило, ближе к короткому переключателю, его проще выразить в виде одной строки. Я часто делаю это с короткими заявлениями о переключениях, а также по тем же причинам. Буквальные if-elseоператоры все еще обычно распределяются по нескольким строкам, хотя и не являются простыми троичными.
Исия Медоуз
@viraptor Технически остальные 50% кода haskell - это «нетривиальные возвраты», потому что все функции haskell функционально чисты и не могут иметь побочных эффектов. Даже функции, которые читают и печатают в командную строку, являются просто длинными операторами возврата.
Фарап
134

Это более читабельно. Несколько причин почему:

  • Почти каждый язык использует этот синтаксис (не все, большинство - ваш пример выглядит как Python)
  • Исанаэ указал в комментарии, что большинство отладчиков основаны на строках (а не на операторах)
  • Это начинает выглядеть еще более уродливым, если вам нужно встроить точки с запятой или фигурные скобки
  • Читается сверху вниз более плавно
  • Это выглядит ужасно нечитаемым, если у вас есть что-то кроме тривиальных операторов return
    • Любой значащий синтаксис отступа теряется, когда вы просматриваете код, поскольку условный код больше не визуально отделен (от Дэна Нили )
    • Это будет особенно плохо, если вы продолжите исправлять / добавлять элементы в 1-строчные операторы if
  • Это только для чтения, если все ваши, если проверки имеют примерно одинаковую длину
  • Это означает, что вы не можете форматировать сложные, если операторы в многострочные операторы, они должны быть однострочными
  • Я гораздо более склонен замечать ошибки / логический поток при чтении по вертикали, не пытаясь разобрать несколько строк вместе
  • Наш мозг читает более узкий и высокий текст НАМНОГО быстрее, чем длинный горизонтальный текст

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

Также люди неизбежно добавляют что-то вроде:

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.

enderland
источник
14
@ Horta, потому что вам придётся сначала конвертировать, если вы сначала отформатируете его? Я все о минимизации работы для будущего enderland. Создание красивых пробельных таблиц и подсчет пробелов и вкладок для обновления визуального форматирования, когда я мог бы добавить некоторую логику к проверке if - это совсем не весело (игнорируя значение читабельности).
enderland
14
@ Horta Он не обязательно говорит, что не исправит это, он говорит, что должен будет это исправить, и это много времени уходит на утомительное форматирование, а не на программирование.
Serv
11
@horta: ИМХО, твой путь часто менее читабелен и определенно намного более раздражает форматированием. Кроме того, он может быть использован только в том случае, если «если» являются маленькими один вкладыш, что редко бывает. И наконец, как-то нехорошо, имхо, носить из-за этого два вида форматирования «если».
августа
9
@ Horta, вы, кажется, наделены работой в системах, где требования, системы, API и потребности пользователей никогда не меняются. Ты счастливая душа.
enderland
11
Я бы добавил: любое небольшое изменение в одном условии может потребовать переформатирования других для соответствия :позиции -> при выполнении сравнения в CVS внезапно становится все труднее понять, что на самом деле меняется. Это также верно для состояния против тела. Наличие их в отдельных строках означает, что если вы измените только один из них, различия ясно показывают, что изменилось только условие, а не тело.
Бакуриу
55

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

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

Арт Сури
источник
3
Соответствующий: thecodelesscode.com/case/94
Кевин
11
Это объясняет, почему люди консервативны. Это не объясняет, почему люди решили написать свой код определенным образом.
Йорген Фог
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
Интересно, но разве это не сбой инструментов слияния? Конкретно мерзавец в этом случае? Это точка данных на конвенцию, являющаяся самым легким путем. Для меня это то, что можно улучшить (со стороны инструмента).
Орта
7
@horta Чтобы инструмент слияния мог модифицировать пробелы таким образом, чтобы почти не всегда нарушать код, он должен понимать, каким образом он может модифицировать пробелы без изменения смысла кода. Он также должен понимать конкретное табличное выравнивание, которое используется. Это будет не только зависеть от языка (Python!), Но, вероятно, потребуется инструмент для понимания кода в некоторой степени. С другой стороны, линейное слияние может быть выполнено без ИИ, и довольно часто даже не нарушает код.
Уэйн Конрад
Попался. Поэтому, как уже упоминалось в комментариях, до тех пор, пока у нас не появятся IDE или форматы ввода программ, которые будут включать таблицы непосредственно в этот формат, проблемы с инструментами всегда будут существовать, только усложняя жизнь тем из нас, кто предпочитает таблицы.
Орта
1
@ Horta Правильно. Большинство моих возражений против табличного выравнивания в коде могут исчезнуть с помощью достаточно продвинутых инструментов.
Уэйн Конрад
8

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

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

Supercat
источник
Правда. Я заметил, что текстовый редактор, который я использую (vim), имеет ужасную поддержку табличного форматирования или даже широкого текста. Я не видел, чтобы другие текстовые редакторы работали намного лучше.
Орта
6

Есть выражение «switch», которое предоставляет подобные вещи для особых случаев, но я думаю, это не то, о чем вы спрашиваете.

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

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

gbjbaanb
источник
1
OP, кажется, использует Python, поэтому нет switch.
Джаред Смит
2
«3, если утверждения лучше всего показывать в традиционном формате, но если у вас было 20» ... тогда у вас есть большие проблемы, о которых стоит подумать! :)
Гримм Opiner
@GrimmTheOpiner Если вы пишете синтаксический анализатор языка или строковый классификатор AST, это очень вероятная вещь. Например, я однажды внес свой вклад в синтаксический анализатор JavaScript, где я разделил функцию на 15-20 случаев, по одному для каждого типа выражения. Я разделил большинство случаев на их собственные функции (для заметного повышения производительности), но долгое время switchбыло необходимостью.
Исия Медоус
@JaredSmith: Очевидно, switchэто зло, но создание словаря, а затем поиск по нему для тривиального ветвления не зло ...
Марк К Коуэн
1
@MarkKCowan о, я поймал сарказм, но подумал, что ты использовал его, чтобы издеваться надо мной. отсутствие контекста в интернете и еще много чего.
Джаред Смит
1

Если ваше выражение действительно простое, большинство языков программирования предлагают оператор?: Branching:

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 );
}
Falco
источник
7
На самом деле, я считаю, что ваш «короткий читаемый табличный формат» - настоящий кошмар для чтения, в то время как формат, предложенный OP, вполне подойдет.
Matteo Italia
@MatteoItalia как насчет этой отредактированной версии?
Falco
5
Извините, еще хуже; Я думаю , что это происходит от того , что ?и :труднее определить , чем if/ elseключевые слова и / или из - за добавленный «шум» символов.
Маттео Италия
@MatteoItalia: у меня были случаи с более чем сотней различных значений. Значение таблицы позволяет проверить на наличие ошибок. С многолинейностью это невозможно.
gnasher729
1
@ gnasher729 - Для 100 различных «значений» я обычно обнаружил, что гораздо лучше объявить структуру данных каждого «элемента» и вывести все это в виде таблицы в инициализации для массива этих структур данных. (Конечно, языковые ограничения могут применяться здесь). Если какой-либо элемент требует «вычислительного» аспекта, структура элемента может содержать указатель или ссылку на функцию для выполнения необходимого действия. Для некоторых приложений это может значительно упростить код и значительно упростить обслуживание.
Майкл Карас
1

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

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

Одна распространенная ошибка, к которой часто обращаются стандарты кодирования, - это маленькая ошибка:

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

Это не делает то, что вы думаете, что делает. Что касается C касается, если х 10 , то вы звоните do_something(), но затем do_something_else()вызывается независимо от значения х . Только действие, следующее за оператором if, является условным. Это может быть тем, что задумал кодер, и в этом случае есть потенциальная ловушка для сопровождающих; или это может быть не то, что задумал кодер, и в этом случае возникает ошибка. Это популярный вопрос интервью.

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

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, который вызывает проблему, о которой вы говорите. При кодировании на C, я бы никогда не рекомендовал использовать альтернативный простой метод if с табличным форматом, который я предложил. Вместо этого, так как вы используете C, вы должны использовать все фигурные скобки в одной строке. Скобки фактически сделали бы формат таблицы еще более ясным, потому что они действуют как разделители.
Орта
Кроме того, операторы возврата в этом случае являются лишь примером. В общем, это может быть запах кода сам по себе. Я просто имею в виду формат простых операторов, а вовсе не обязательно с операторами возврата.
Орта
2
Я хотел сказать, что это делает формат таблицы еще более неуклюжим. Между прочим, он не специфичен для C - он используется всеми языками, производными от C, поэтому C ++, C #, Java и JavaScript все допускают одно и то же.
Грэм
1
Я не против, чтобы это были заявления о возврате - я понял, что вы намерены показать простые заявления. Но это становится более громоздким. И, конечно, как только любое утверждение становится непростым, вам необходимо изменить форматирование, поскольку формат таблицы невозможно поддерживать. Если вы не делаете запутывание кода, длинные строки кода сами по себе являются запахом. (Первоначальный лимит составлял 80 символов, в наши дни он обычно составляет около 130 символов, но общий принцип по-прежнему состоит в том, что вам не нужно прокручивать, чтобы увидеть конец строки.)
Грэм
1

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

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

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

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

if i==0: return 0
return i>0?sqrt(i):(1j*sqrt(-i))
hildred
источник
1
Возможно, вам придется прояснить, что означает «?:» Для непосвященных (например, с примером, возможно связанным с вопросом).
Питер Мортенсен
Я предполагаю, что это троичный оператор. Я чувствую, что этого избегают по той простой причине, что троичный оператор имеет тенденцию перестраивать стандарт, если, занимаясь чем-то другим, делай другие вещи в формате, который люди видят изо дня в день и, следовательно, могут легко читать.
Орта
@PeterMortensen: Если непосвященные не знают, что это значит, они должны держаться подальше от кода, пока не зададут очевидный вопрос и не научатся.
gnasher729
@ Horta Ternary является if ? do stuff : do other stuff. Тот же порядок, что и if / else.
Навин
1
@ Навин Ах, может быть, это просто провал языка, который я использую больше всего (python). stackoverflow.com/questions/394809/…
Орта
-3

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

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

Не нужно повторять returnкаждый раз :)

Navin
источник
1
Как упомянуто в нескольких комментариях, операторы return не идеальны или смысл статьи, это просто кусок кода, который я нашел в Интернете и отформатировал несколькими способами.
Орта
Питон тройники есть do_something() if condition() else do_something_else(), а не condition() ? do_something() : do_something_else().
Исия Медоуз
@IsiahMeadows OP никогда не упоминал Python.
Навин