пятница, 9 ноября 2012 г.

Точки над константами слева.

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

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

Но два требования вызвали у нас серьёзный протест: окружать тело однострочных блоков условий и циклов фигурными скобками и писать в любых сравнениях константы слева. (Основным языком курса был C++) Основная мотивация ясна. Первое правило устраняет возможность ошибки при дописывании новой строчки к однострочному блоку и забывании таки окружить его при этом фигурными скобками. Второе ставит своей основной целью предотвратить ошибочное написание «=» вместо «==» в условиях, типа if(count = 0).

Если первое представляет из себя, по сути дела, мелочь, и протест был довольно вялым, то по второму вопросу разгорались особенно жёсткие споры, вызванные, в основном, резким снижением читабельности «леворульных» условий, особенно, если они посложнее и подлиннее вышеприведённого примера, или если в них не равенство, а неравенство (при котором ошибиться вообще сложно). В числе главных непримиримых «ценителей» красивого кода оказались ваш покорный слуга и Вова Парфиненко. Забавным результатом этой непримиримости стала разработка последним специальной утилиты "Make It Ugly" (MIU), которая позволяла свой «красивый» код перед самой сдачей превратить в «некрасивый», но подходящий под требования. Мы тогда очень гордились своей хитростью и тем, что не сдались, сейчас вспоминать об этом, конечно, забавно :) В любом случае, это было полезным упражнением в обработке текста регулярными выражениями.

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

Итак, вот эта дискуссия.
(Форматирование моё, орфография за редкими исключениями сохранена авторская)


Женя Козлов:

Константы справа - сейчас уже неактуально, когда компиляторы/IDE/статические анализаторы делают до фига работы за программиста. И скобочки за меня IDE сама ставит

Денис Гладкий:

Соратники, я тут FB не юзаю, поэтому в "личку" напишу :)

Вы можете кому угодно и сколько угодно доказывать про термо-ядерные новейшие компиляторы и "я никогда не лажаю, статический анализатор всё за меня сделает", но есть факты:

  1. В этом (2012-м !!) году в СофтЛабе ещё были проекты на Visual Studio 7.0 (это которая даже не 2003-я, вы, поди, такую и не застали уже).
  2. На сервере ФИТ стоит gcc, который даже с флагом -Wall не выдаёт ворнингов на if (a = 1). Мало того, что на нём кучу лаб ФИТ сдаёт, так там и реальные проекты разрабатываются.
  3. Далеко не все проекты компилируются полностью без warnings или с -Wall/аналогами, а это значит нифига не нулевую вероятность того, что ворнинг, таки прощёлкается.
  4. Прощёлкать ворнинг можно даже в идеальном проекте: компиляем, видим ворнинг, "аааа... сейчас лень править... вот, отлажусь и поправлю...", оба! а obj-то уже есть и при следующей инкрементальной компиляции мы ворнинга не видим и успешно его просираем :) (причём, пример нифига не надуманный, а из жизни).
  5. C# / Java : "foo".equals(someStringReference) - константа у нас где? Я ещё могу согласиться, что в этих языках if (0 != a) может вызвать культурный шок, но C/C++ они "от рождения больны" - по понятным причинам там всегда будет разрешена компиляция if (a = 1), а значит всегда будет вероятность накосячить.
  6. Вы такие мега-спецы, что видели компиляторы под PS3/XBox360/PSP/PSVita/Wii/Wii U? ;) Зная подходы Sony и Nindendo, думаю, что там всё ещё хуже, чем с VS 7.0. Но тут точно не могу сказать - через год расскажу ;)

1) В этом (2012-м !!) году в СофтЛабе ещё были проекты на Visual Studio 7.0 (это которая даже не 2003-я, вы, поди, такую и не застали уже).
Прогал под Visual Studio 6.0, правда чуть-чуть совсем

2) На сервере ФИТ стоит gcc, который даже с флагом -Wall не выдаёт ворнингов на if (a = 1). Мало того, что на нём кучу лаб ФИТ сдаёт, так там и реальные проекты разрабатываются.
Скажи, а почему получается так, что юнит-тесты проходят на тот метод, где допущена ошибка if (a = 1)? Ах да, на ФИТе их не учат писать (или все-таки учат?). В итоге, выпускник ФИТа с красным дипломом знает, что нужно писать константы слева и умеет за ночь наколбасить 100500 кода с мега-классами на 3000 строк. Код работающий и даже, может быть, кем-то будет использоваться в будущем. Но поддерживать такое...  Декомпозиция функционала? Не, не слышали. (Я не защищаю АФТИ, там не лучше в этом плане).

3) Далеко не все проекты компилируются полностью без warnings или с -Wall/аналогами, а это значит нифига не нулевую вероятность того, что ворнинг, таки прощёлкается.
А почему получается так, что ворнинг не этапе набора кода детектируется? Я вот Eclipse сразу вижу ошибку if (a = 1) и тут же ее исправляю. Ах да, gcc, vim - наше всё.

4) Прощёлкать ворнинг можно даже в идеальном проекте: компиляем, видим ворнинг, "аааа... сейчас лень править... вот, отлажусь и поправлю...", оба! а obj-то уже есть и при следующей инкрементальной компиляции мы ворнинга не видим и успешно его просираем :) (причём, пример нифига не надуманный, а из жизни).
См. коммент выше.

5) C# / Java : "foo".equals(someStringReference) - константа у нас где? Я ещё могу согласиться, что в этих языках if (0 != a) может вызвать культурный шок, но C/C++ они "от рождения больны" - по понятным причинам там всегда будет разрешена компиляция if (a = 1), а значит всегда будет вероятность накосячить.
В Java 7 есть класс java.util.Objects с методом equal(Obj obj1, Obj obj2), где NPE вылететь не может. В Java 6 можно подключить библиотеку guava с аналогичным классом, либо написать такой класс самому (10 строк кода).

6) Вы такие мега-спецы, что видели компиляторы под PS3/XBox360/PSP/PSVita/Wii/Wii U? ;) Зная подходы Sony и Nindendo, думаю, что там всё ещё хуже, чем с VS 7.0. Но тут точно не могу сказать - через год расскажу ;)
Это проблема конкретных компиляторов. И опять же, пиши юнит-тесты.

Денис Гладкий:


Прогал под Visual Studio 6.0, правда чуть-чуть совсем
Чуваааак %)

Скажи, а почему получается так, что юнит-тесты проходят на тот метод, где допущена ошибка if (a = 1)? Ах да, на ФИТе их не учат писать (или все-таки учат?). В итоге, выпускник ФИТа с красным дипломом знает, что нужно писать константы слева и умеет за ночь наколбасить 100500 кода с мега-классами на 3000 строк. Код работающий и даже, может быть, кем-то будет использоваться в будущем. Но поддерживать такое...  Декомпозиция функционала? Не, не слышали. (Я не защищаю АФТИ, там не лучше в этом плане).
Ну это уже совсем наброс. Мы тут обсуждаем конкретную практику программирования, а не то, как, где и чему учат. Если что (если кому не очевидно), то "константы слева" - это не всё, чему учат на ФИТе. Я работаю уже в 3-й конторе и собеседовал около полусотни человек, и далеко не пару раз вёл пары на ФИТ и АФТИ. И честно тебе скажу, что ФИТ - это лучшее, что случалось с местным программированием. + есть отзывы посонов из Luxoft (для дойче-банка кодят) - они говорят, что выпускники ФИТа лучше МГУшных, но на равне с МФТИ. Предлагаю, не отвлекаться от констант с лева.

А почему получается так, что ворнинг не этапе набора кода детектируется? Я вот Eclipse сразу вижу ошибку if (a = 1) и тут же ее исправляю. Ах да, gcc, vim - наше всё.
Ты в Eclipse на С++ прогаешь? Я не говорю что vim/gcc - круть, но, если что, пардоньте, а чем ты собрался компилять под платформу (те же консоли), пот которую тебе выдали один единственный компилятор и других нет в принципе? И кстати да, я знаю GameDev-конторы где на уровне правил работы можно писать только в vim. И да, контора имеет мировое имя, выпустила гаму на стиме, их движок делается под PC/XBox/PS3. Женя, я понимаю что есть куча всего нового крутого и правильного. НО. С++ - это по большей части язык легаси-проектов. У тебя, кроме наколеночных домашних поделок, никогда не будет счастья имень самый свежий тулсет. И да, это счастье писать в VS 2008. И всё что тут можно поделать - искать "правильную" контору.

В Java 7 есть класс java.util.Objects с методом equal(Obj obj1, Obj obj2), где NPE вылететь не может. В Java 6 можно подключить библиотеку guava с аналогичным классом, либо написать такой класс самому (10 строк кода).
Отлично. Весь мир переехал на Java7? Если что, в андроиде 6-я, и ещё в кууууче проектов по разным причинам 1.4-я, 5-я или того хуже 1.3. Опять же, рад за тебя, если ты пишешь в IDEA 15 на Java 10. Но, блин, мир-то не всегда такой, какой ты хочешь :)

Это проблема конкретных компиляторов. И опять же, пиши юнит-тесты.
Юнит-тесты не панацея, бессмысленны в легаси-проектах на сотни мегабайт кода (как раз - то где царит c/c++), зачастую тормозят разработку.

Денис Гладкий:


Кстати, парни, если хотите ещё больше "оскорбиться непотребным кодом", то вот:


Интересно, найдёте ли некую технику, которую автор пользует? Техника дюже нестандартная но очень в духе констант слева, я её даже студентам не сильно пропагандирую :)


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

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

Во-вторых, если IDE всё-таки не новая, но компилятор адекватный, то я выловлю ворнинг на этапе компиляции. Если
"аааа... сейчас лень править... вот, отлажусь и поправлю..."
— то надо бороться с этой ленью, а не с константами справа. Когда я работал в UGENE, при компиляции (особенно при пересборке, которую приходилось делать периодически из-за тупняков с определением зависимостей в gcc) могло высыпаться по 300 легаси-ворнингов, и я всё равно внимательно просматривал, какие из них действительно легаси, а какие привнёс я.

В-третьих, если на сервере ФИТа стоит старый gcc, можно было бы, по крайней мере, попробовать вежливо попинать кого-то, чтоб обновили. Не получится — ладно, зато как всем хорошо стало бы, если бы получилось! Я к тому, что ворнинг на константы — это явно не единственное, что появилось в новой версии. Опять же, в некоторых случаях, даже если вся команда сидит на одном старом компиляторе, это не сильно мешает самому разрабатывать на более новом. При наличии хорошей автоматизированной билд-системы все проблемы из-за несовместимости компиляторов выявятся ещё на прекоммите. Опять же, примерно так и было в UGENE.

В-четвёртых, если компилятор не ворнингует, либо это вообще не компилятор, а интерпретатор скриптового языка (Ruby, Python), я буду, опять же как Женя, полагаться на тесты. Юнит ли, функциональные ли, имхо их надо писать, особенно в случае интерпретаторов. Опять же, для этого необязательно заставлять всю контору писать их (хотя попробовать стоит), если это и правда затормозит разработку. Можно спокойно покрыть тестами один свой модуль и радоваться. Я, наверное, задолбал с этим примером, но да, в UGENE с юнит-тестами было примерно так: они исторически покрывали только один модуль, но уж лучше так, чем совсем без них. Функциональными же и GUI тестами (как автоматизированными, так и ручными) старались покрывать всё, и это являлось чуть ли не основным источником фиксов тупых багов.

Наконец, если все звёзды сошлись неудачно, и ни одно из четырёх утверждений неверно — точно не знаю. С таким я ещё не сталкивался. Предположу, что я либо утроил бы внимание, либо утроил бы тщательность тестирования, либо как last resort, действительно, в этом конкретном проекте стал бы писать константы слева. Но я всё равно не понимаю, зачем при этом заставлять себя мучиться во всех остальных? Зачем весь свой кодстайл загонять под одну гребёнку, если он всё равно принципиально разный у разных языков и даже фреймворков? Короче говоря, я за то, чтобы использовать любой приём, в том числе константы слева, только там, где нужно. А то это уже золотым молотком попахивает. Это касается и примера из джавы. Да, при использовании метода equals константу лучше писать слева из соображений безопасности от NPE. Но как из этого следует, что при НЕиспользовании метода equals и вообще в других языках мы всё равно должны делать то же самое?

Конечно, я понимаю, что несмотря на это, заставлять себя ВЕЗДЕ писать константы лучше в плане дисциплины. Завёл привычку — можно отключить мозги. Согласен, что такая точка зрения имеет право на жизнь. Но я её не принимаю. Мне больше нравится держать мозги включенными. Полезная тренировка.

А насчёт...
Кстати, парни, если хотите ещё больше "оскорбиться непотребным кодом", то вот: (ссылка)
Интересно, найдёте ли некую технику, которую автор пользует? Техника дюже нестандартная но очень в духе констант слева, я её даже студентам не сильно пропагандирую :)
Если ты о if (e.NewValue is bool == false), то есть о сравнении с false вместо приписывания ! перед условием — то я не скажу, что это непотребный код. Встретив эту технику впервые, кажется, в этих самых доках, я перенял её себе в изменённом виде. Когда у меня есть функция, делающая нечто (например, tryClosing() ) и при этом возвращающая false в случае ошибки, то проверять этот код возврата в виде if( ! tryClosing() ) мне не нравится, потому что по-русски получается "если не попробовать закрыть", в то время как по логике должно быть "если попробовал закрыть, и не получилось". Тогда я пишу if( false == tryClosing() ), и да, здесь константа идёт слева, потому что в данном конкретном случае она имеет большее значение для if'а, чем аргументы функции и всё остальное, что там идёт в конце. И опять же, хотя эта техника сравнения с false применима в некоторых редких случаях вроде этого, я не склонен практиковать её где-то ещё (снова помним про молоток). В частности, причина применения её в примере по ссылке мне не до конца понятна.
Вообще, я не ставил здесь цели кого-то переубедить, потому что понимаю, что если кто-то как-то делает всю жизнь, то ему в любом случае эффективнее продолжать делать так же. А поскольку в моём случае про "всю жизнь" говорить ещё рановато, может, когда-нибудь я переменю своё мнение, но не сейчас :)

Денис Гладкий:


Про false == ...
Да именно про это намекал. Основная задумка этой техники: восклицательный знак - маленький символ, который плохо видно, а false - литерал, который ещё и IDE подсвечивает. То есть попытка улучшить читабельность кода. Затем мужик там это и писал =)

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

Про золотой молоток.
"Константа слева" - правило для решения конкретных проблем в конкретных местах. Да, так вышло, что мест этих много. Ты же не делаешь в жизни так: "О! Трава! А по ней босяком можно! Снимаем ботинки!" Ты, всё-таки, если надел ботинки, то снимаешь, только по концу прогулки, а не когда можно без них идти. Ботинки же не становятся от этого золотым молотком и люди их носящие не выглядят фанатиками тупыми? ;)

Про false == ...
Да именно про это намекал. Основная задумка этой техники: восклицательный знак - маленький символ, который плохо видно, а false - литерал, который ещё и IDE подсвечивает. То есть попытка улучшить читабельность кода. Затем мужик там это и писал =)
Согласен, это может быть оправданно. В свою очередь, я нашёл другое решение: просто отбиваю восклицательный знак с обеих сторон пробелами (в предыдущем письме есть пример этого). Мелочь, а уже становится заметнее, потому что "!" не сливается с "(". Впрочем, против этой техники ничего не имею. Полезно и оригинально. Если я буду читать код, где она повсеместно используется, я не буду проклинать писавшего :)

Про мозг. 
А никто и не говорил, что его нужно отключать везде. Просто, чем больше ты контролируешь вещей, тем меньше твоего "процессорного времени" (ну или внимания, хотя непонятно, чем его померить), достаётся каждой из них. Мозг включённым, безусловно, надо держать, но на нужные вещи. Вот, сам посмотри, какая у тебя портянка из правил и контекстов, где писать константу слева, а где не писать :) Не проще ли одно тупое правило?
Я, наверное, недостаточно чётко сформулировал основную мысль своей портянки, попробую исправиться. Я рассмотрел четыре самых распространённых случая из своей практики, и ни в одном мне не нужны были константы слева. Потом я показал, что в двух очень редких (для меня) исключениях я может (МОЖЕТ!) воспользуюсь константами слева. А может и не воспользуюсь, потому что мне может оказаться проще в этих крайне редких случаях просто быть более внимательным. Так что груз правил тебе где-то померещился.

Более того, открою свой небольшой секрет. Не только в случае констант, но и вообще по жизни, у меня практически нет чётко сформулированной системы правил хорошего кода. Я (по крайней мере мне так кажется) за годы университета, когда мой кодстайл многократно реформировался, выработал не столько правила, сколько ощущение хорошего кода, основанное, преимущественно, на ассоциации с виденными мною примерами кода "крутых отцов" (в числе которых Олег Дашевский, Никитин, создатели Rails и даже Парф), который я считал в большей или меньшей степени аксиоматически хорошим. Так что когда я кодю теперь, я не прокручиваю в голове правила. Я просто пишу, как пишется, потом критически смотрю на свой код, и если моё эстетическое чувство меня где-то беспокоит, я либо немедленно рефакторю, либо (если, и только если сроки поджимают) ставлю TODOшечку, чтобы вернуться, когда времени будет больше. И всё. Мне кажется, это даже проще чем одно тупое правило, потому что работает где-то в параллельном полушарии по ходу набора кода. То есть "процессорное время" моего мозга экономится за счёт переноса части задачи на GPU :)

Про золотой молоток. 
"Константа слева" - правило для решения конкретных проблем в конкретных местах. Да, так вышло, что мест этих много. Ты же не делаешь в жизни так: "О! Трава! А по ней босяком можно! Снимаем ботинки!" Ты, всё-таки, если надел ботинки, то снимаешь, только по концу прогулки, а не когда можно без них идти. Ботинки же не становятся от этого золотым молотком и люди их носящие не выглядят фанатиками тупыми? ;)
Пример не совсем подходящий под мою мысль. Если сформулировать её в тех же терминах травы и ботинок, то она такова: если бы мир состоял на 99,9% из травы, то да, я бы в жопу послал ботинки и всегда ходил бы босиком, а уж по 0.1% асфальта как нибудь уж пропрыгал, на месте бы разобрался. То есть, если в 99.9% случаев моей жизни константы слева не нужны — я и не пользуюсь. Если в твоём мире (то есть в твоей профессиональной деятельности) процентное соотношение травы и асфальта прямо противоположное — то я не осуждаю тебя за ношение ботинок, ибо это единственно правильное решение. Но не надо на основании этого устраивать агрессивный маркетинг ботинок в моём мире, где этот асфальт встречается только в зоопарке =) А на 3 курсе примерно это и происходило :)

Денис Гладкий:


Про маркетинг. 
Ну, вот сейчас, после получения диплома, после херачения в унипро ты повидал всякого и я рад, что можешь нормально аргументировать свою позицию. А на 3-м курсе, большая часть из вас только и могла сказать на константы слева: "0_o нам такого не показывали, и вообще бьют за попытку взять банан" (про банан - это есть схема объясняющая понятие "тут так принято"). И, что немаловажно, некоторые в таком состоянии останутся до конца своей программесркой деятельности :-\ Хорошо, если какой-нить Парф и Новиков могли проворчать: "а в Ruby нет таких проблем и вообще мне сложно такой код читать". Вот для такого состояния, как раз и надо "константы всегда слева". Те, кто ленивый или тупой, не будут ковыряться а просто сделают и хорошо, если привыкнут и будут дальше так писать. Остальные либо проникнуться идеей и будут писать, либо, как ты, аргументированно не будут. Сплошные плюсы :)

Про false == ...
Эту штуковину студентам надо ооочень аккуратно давать. Я ещё ни разу не выдвигал это дело, как обязательное к исполнению. Но народ иногда начинал с false простые булевые переменные сравнивать %) Я уже думал, что лучше б не рассказывал вообще.


На этом всё :) Если вам есть что добавить — добро пожаловать в комментарии :)

Комментариев нет:

Отправить комментарий