Pull to refresh

Как я перестал тревожиться из-за читабельности и… все

Level of difficultyEasy
Reading time12 min
Views32K

Всем привет.

Давно читаю хабр, давно и регулярно читаю статьи про правильное программирование.

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

Прежде о себе: более 20 лет программирую на С/С++/Objective-C, в разной степени знаком с десятком других языков.

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

А теперь к делу. TLDR: забейте на читабельность и выразительность. Стремитесь сделать ваш код хорошо работающим..

Мне много раз приходится читать чужой код. Вот даже прям вчера. И позавчера. И завтра придется. Скажу вам честно: мне важно его понять. Важно знать что там творится. Умные люди для этого советуют использовать говорящие имена переменных, всякие фичи языка, отступы, и тому подобное. К сожалению, мне эти рецепты совершенно не помогают.

Вот к каким выводам я пришел:

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

Эти показатели важны

Субъективные - все остальные показатели, которые трудно выразить числами или они влияют в основном на настроение программиста. 

Субъективные показатели не важны.

  1. Говорящие имена переменных бесполезны, потому что длинные наборы слитых вместе слов ну просто никак не способствуют пониманию. Лично меня это упражнение по спортивному чтению утомляет и запутывает. А уж дебажить с такими чудовищами и читать крашдампы... Не, спасибо, не надо.

  2. Именам всегда нужен контекст. Они же не просто так используются, а как часть алгоритма. И алгоритм изучить занимает значительно больше времени, чем расшифровывать гигантские названия. А после того как алгоритм понят, на говорящие названия уже плевать, ну или не плевать, но в любом случае выгоды от них практически никакой. Вот, например, мы видим такое выражение:

    double rotor_drag=1;

    Имя говорящее? Да. Но что именно оно нам говорит?

    Что здесь означает слово “rotor”?

    Что здесь означает слово “drag”?

    Что означает их сочетание в этом месте?

    Почему эти слова идут именно в таком порядке?

    Если мы создадим переменную с обратным порядком слов в названии, она будет иметь иное значение или то же самое?

    Какие единицы тут используются?

    В каких границах может изменяться значение?

    Значение дискретное или непрерывное?

    Чтобы ответить на эти вопросы, надо знать, частью какого алгоритма это выражение является. А еще лучше знать, какая проблема здесь решается, и из какой предметной области. То есть чтобы понять, зачем это словосочетание здесь появилось, надо иметь намного больше информации, чем заключено в нем самом! Как запихать всю эту информацию в название переменной? Да никак. Не нужно даже и пытаться. Так что же именно мы выигрываем, сражаясь за "говорящее" название? Боюсь, так мало, что не стоит тратить на него много времени. Можно написать “rd”, и толку будет не меньше. 

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

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

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

  6. Я лично не понимаю смысл слова "выразительность" для этого контекста. Что это значит? Что может быть выразительней конструкций “if-else, for и while”? Что именно вы пытаетесь выразить в коде? Глубину чувств? Величие момента? Экзистенциальную усталость?

    “If-else” с друзьями это базовые кирпичики для построения алгоритмов. В фунциональных языках есть еще fold, map, filter, lambda; что еще для построения алгоритмов необходимо? Ваш код текстово описывает на языке программирования алгоритм, точка. Техт транслируется компилятором с ЯП в машинный код. Точка. В каком месте тут возникает необходимость в выразительности? 

    Я почему такое говорю: приходилось мне читать код, который бывалые люди считали выразительным. И невыразительный тоже приходилось читать. Скажу честно - разницы между ними я почти не видел. Наверно я тупой, но лично для меня выразительный код такая же нагрузка на мозг, как и любой другой. В него тоже приходится вгрызаться, и соображать что там к чему и зачем. И забывается он так же быстро. Так что в чем профит с выразительности, я простите так и не понял за столько лет работы. Не скажу за всех, но лично для меня 10 строк кода уже нечитабельны потому что приходится напрягаться, чтобы понять что они делают. Большое количество текста сводит читабельность до нуля.

  7. Но то я. А ведь есть наверное другие люди, значительно более умные, будут читать ваш код и грустить, потому что он же невыразительный! Ну что тут сказать: см. пп.0,1,2,3,4,5,6. Возможно это неубедительно, но зайдем с другого боку: ваш код не читают от нечего делать, в качестве развлечения. Ну почти никто. В него смотрят обычно намереваясь обнаруживать и чинить баги. Я могу заверить, исходя из собственного опыта: намного важнее выразительности понимание алгоритма. Мне неоднократно приходилось чинить чужие баги в немалой по объему программе (5-6К файлов), так вот: когда алгоритм раскидан по паре десятков файлов, когда эти файлы набиты продуктами жизнедеятельности десятка программеров, о выразительности вообще не думается. Мне важнее видеть историю изменений, понимать суть алгоритма, знать, куда какой вызов передается, в какое время и с какими параметрами.

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

    Выразительность? Не смешите меня. Лучше писать тщательно, обдуманно, отыскивать собственные ошибки, вылизывать, и если будет достигнуто абсолютно корректное исполнение вашего кода, то никому его читать и не придется. В конце концов, кто в здравом уме лезет читать исходники STL? Дураков нет, работает и слава богу.

    Давайте рассмотрим такой пример: 

if (__pos == 0)
        {
            __alloc_traits::construct(__a, _VSTD::addressof(*--__base::begin()), __v);
            --__base::__start_;
            ++__base::size();
        }
        else
        {
            const_pointer __vt = pointer_traits<const_pointer>::pointer_to(__v);
            iterator __b = __base::begin();
            iterator __bm1 = _VSTD::prev(__b);
            if (__vt == pointer_traits<const_pointer>::pointer_to(*__b))
                __vt = pointer_traits<const_pointer>::pointer_to(*__bm1);
            __alloc_traits::construct(__a, _VSTD::addressof(*__bm1), _VSTD::move(*__b));
            --__base::__start_;
            ++__base::size();
            if (__pos > 1)
                __b = __move_and_check(_VSTD::next(__b), __b + __pos, __b, __vt);
            *__b = *__vt;
        }

Выразителен ли такой код?

Как поменять в нем названия переменных, чтоб он стал читабельнее?

А как насчет такого кода?

for(i=0; i<argc; i++){
    int64 x;
    unsigned c;
    x = value_int64(argv[i]);
    if( x<0 || x>0x10ffff ) x = 0xfffd;
    c = (unsigned)(x & 0x1fffff);
    if( c<0x00080 ){
      *zOut++ = (u8)(c&0xFF);
    }else if( c<0x00800 ){
      *zOut++ = 0xC0 + (u8)((c>>6)&0x1F);
      *zOut++ = 0x80 + (u8)(c & 0x3F);
    }else if( c<0x10000 ){
      *zOut++ = 0xE0 + (u8)((c>>12)&0x0F);
      *zOut++ = 0x80 + (u8)((c>>6) & 0x3F);
      *zOut++ = 0x80 + (u8)(c & 0x3F);
    }else{
      *zOut++ = 0xF0 + (u8)((c>>18) & 0x07);
      *zOut++ = 0x80 + (u8)((c>>12) & 0x3F);
      *zOut++ = 0x80 + (u8)((c>>6) & 0x3F);
      *zOut++ = 0x80 + (u8)(c & 0x3F);
    }                                                    \
  }

Выразителен? Читабелен? Как по мне, так нет.

А откуда эти чудесные образцы взяты, знаете?

первый отрывок взят из STL а второй из SQLite. Обе библиотеки прекрасно работают и никто в здравом уме этот код не читает, кроме мейнтейнеров, но они похоже от отсутствия читабельности не страдают, их и так устраивает.

  1. Есть еще такое мнение - код нужно постоянно рефакторить и улучшать.

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

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

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

    Так что снова - лучше сразу писать хорошо и фокусироваться на важных вещах, а не названиях переменных.

  2. Что важнее: читабельность с выразительностью - или корректное и быстрое исполнение, короткая компиляция, минимальное потребление ресурсов? Ответ очевиден, как мне кажется.

  3. Отдельно хотелось бы выразиться насчет популярного восхищения внесением эстетических фич в ядро языка. Как например отступы в Питоне и Хаскеле, auto в С++ ... Не могу выразить обуревающих меня чувств. Серьезно, это же отвратно и тупо. Ладно еще питон, там этот идиотизм был заложен изначально, но зачем было в С++ втаскивать на уровне языка ненужную в общем-то приблуду? Он и так сложный и плохо спроектированный, зачем же попусту корежить компилятор, пересматривая семантику ключевого слова? Эх.

Так какие же рекомендации правильного программинга я для себя выработал? Отвечу:

  1. Надо комментировать. Не надо стесняться комментариев. Компилятор их игнорирует, а лично вам они очень помогут. Я припоминаю, как тут недавно проскочила публикация про самостоятельный проект. Автор сделал неплохую работу, заморочился графиками, диаграммами, код привел, подробно описал все модули, в самые кишки залез - а в комментариях ему попеняли на неправильные имена переменных и, внимание - слишком! много! комментариев! Рукалицо. Ну даже обидно немного стало за автора.

  2. Надо комментировать функции в javadoc стиле; по крайней мере те, что выставляются наружу. Это не пожелание, это требование. Ведь код, оформленный таким образом можно автоматически просканировать, скомпилировать документацию в человекочитаемом (читабельном!) формате и использовать отдельно от самого кода! Это необходимо, и тот кто этого не понимает, вероятно профессионально не состоялся.

  3. Мне скажут: но зачем же морочиться javadoc - стилем? Если программисту важно знать, что происходит внутри чужого модуля, то он просто заглянет вовнутрь и сразу все узнает. Нет! К чужому модулю следует относиться как к черному ящику, и единственным способом узнать, что внутри, будет чтение javadoc-а. По-умному это называется "контракт" и является фундаментальной характеристикой взрослого программирования. В работающий код посторонние ходить не должны, за исключением случаев, когда это помогает понять свой собственный. Конечно, когда модуль работает плохо, а надо чтоб хорошо, тогда приходится лезть и подкручивать. Но это уже на собственный страх и риск и нечасто. Кроме того, javadoc стиль необходим для автоматической генерации документации. А документация существующая отдельным файлом вообще сказка. Ее можно читать и узнавать все жизненно важные сведения, не глядя вообще в код. Смотрите пункт про STL и SQLite.

  4. Не надо думать о тех, кому придется читать ваш код - не надо жертвовать корректной работой и прочими объективными техническими параметрами ради мифического комфорта потенциальных критиков. Если это просто туристы, то их никто читать не заставляет, пускай идут лесом. А если читать будут профессионалы по нужде, то поморщатся, но прочитают и сделают то что нужно. По-любому ведь всем не угодишь, потому нет смысла сильно напрягаться. Проиллюстрирую это следующим образом: например, топовые мобильники вроде айфона. Их ремонтопригодность уже давно ниже всякой критики. Хорошо ли это? Не знаю, но думаю что это просто неизбежно - адски сложные системы, оптимизированные по самые уши, герметизированные, набитые сложнейшей электроникой, производимой поштучно (здесь я имею в виду, что электроника не из разряда широко распространенных на рынке комплектующих, а изготовляется под конкретный прибор ровно в количестве, необходимом для производства); изготовлены на очень высоком технологическом уровне, с высокими требованиями по качеству и расходу ресурсов. Может ли техник со стороны что-либо починить в том же айфоне, не имея доступа к ресурсам изготовителя? Не может, скорее всего. У него не для этого ни квалификации, ни ресурсов. Так зачем проектировать тот же айфон с мыслью о том, что левый техник заглянет вовнутрь, ничего не поймет и ничего не сможет починить? Незачем. Если хочет уметь чинить, пусть пройдет сертификацию. В конце концов, никого же не беспокоит что Вася с улицы не может починить электронный микроскоп или известную машину для производства чипов. 

  5. Надо документировать. Документируйте алгоритмы, документируйте конфигурации, документируйте инструкции, архитектуру, подсистемы, модули и библиотеки, формальные требования, планы тестирования и результаты. Документируйте как можно больше. Все эти инфантильные мульки про самодокументирующийся код надо выбросить на помойку. Самодокументирующимся может быть только что-то уровня “HelloWorld”. Конечно, если документация нежелательна по коммерческим причинам, то можно и не документировать.

    Но по умолчанию - необходимо. Хотя бы для того, чтоб автор мог вспомнить что он натворил, вернувшись после перерыва.

  6. Кто-то из великих когда-то сказанул, что "писать код нужно так, как будто его потом будет читать маньяк, знающий, где вы живете." Это остроумно, но я предложу более вызывающую версию: "Пишите так, чтобы ваш код больше не нужно было читать. Вообще никогда.". Лучше поставить себе такую цель, чем заботиться о выразительности. Я думаю, что программист-маньяк придет по вашу душу намного скорее, если вдруг узнает, что ваш великолепный код имеет какие-то недокументированные особенности, сильно усложняющие жизнь; и чтобы узнать о них побольше он будет вынужден лопатить кучу форумов и переписываться с десятком людей. Можно конечно сказать: ну так прочти код и все поймешь! Он же выразительный! Увы, часто для этого нет времени, ни возможностей - например, если чужой код существует только в бинарном виде. Такое бывает, да. Почему бы не задокументировать свой код, обозначив все важные моменты? 

  7. В это трудно поверить, но большинство людей имеют разные эстетические предпочтения.

    Поэтому призывая к выразительности, вы всегда говорите: "сделайте ваш код выразительным в моем понимании". Но ваше понимание, увы, никому, кроме вас, недоступно. Поэтому, то что вам покажется нечитабельным, возможно будет читабельным для кого-то другого. 

    Eсли вы хотите чтоб ваш код сертифицировался, продавался за приличные деньги и использовался максимально широко, то надо его описывать как можно тщательнее по общепринятым стандартам и интенсивно тестировать. Берите пример с Эскулайта, это реально заслуживающий восхищения проект. Вообще, следует писать так, чтобы ваш код можно было встроить в (почти) любое легаси. Ведь вполне может стать так, что гонка за модой когда-нибудь прекратится и профессиональные разработчики просто застрянут в одной версии и откажутся переползать на более новые. Хотелось бы чтоб они застряли на версии 2011, но это уж как получится.

  8. Писать надо просто. Без новомодных добавок в языки, (я намекаю на С++), без фич, легко приводящих к неопределенному поведению, усложняющих компиляцию и дебаг. Без демонстрации собственной крутизны. Но так, чтобы экономить время, память и прочие важные ресурсы. Традиционный набор правил для качественного С++ программера состоит, как ни парадоксально, из запретов: не используй множественное наследование, исключения, перегрузку операторов, RTTI. И это правильно - рядом с языком с огромным количеством способов выстрелить себе в ногу, надо все-таки вести себя осторожнее и соблюдать дисциплину. Я бы еще добавил кое-что: не использовать auto, constexpr, не слишком увлекаться шаблонами и еще много всяких модных приблуд просто игнорировать. Скажу крамольно: вообще-то самый важный прорыв в С++ был сделан в стандарте 2011, когда были введены атомики, мутексы, треды и новая модель памяти; только им и имеет смысл пользоваться, все последующие версии только вносят шум и новые проекты на них писать лучше не надо. Я имею в виду, серьезные проекты, а не детские самодокументирующиеся упражнения. Ничего жизненно важного и прорывного в последующих версиях нет, просто тупая ползучая питонизация. Лучше оставить в покое С++, прекратить его бесконечно улучшать и сводить нас с ума новыми стандартами каждые 3-5 лет.

  9. Писать надо экономно. Нужно беречь ресурсы. Нельзя говорить "клиент мол просто возьмет железо помощнее да еще 100Тб памяти". Деньгами клиента распоряжается только сам клиент, и не стоит ожидать, что он сейчас побежит вытаскивать из заначки дополнительные деньги, потому что вы ради читабельности сделали вашу программу в 100 медленнее и в 10 раз более требовательной к памяти, чем она могла бы быть.

Отмечу, что я не против стилевых руководств (coding style guides), но лишь до тех пор, пока они занимаются техническими реалиями, а не читабельностью и подобным феншуем.

На этом пока все, спасибо за внимание.

PS. В комментариях мне встретился такой аргумент: мол, программирование есть социальная активность, поэтому ... ну вы понимаете.
Я думаю, что любая активность, в которую вовлечены 2 и более человек, социальна по сути.
Но тут опять возникает вопрос расстановки приоритетов.
Члены футбольной команды во время игры не думают о том, как сделать коллегам приятно; они думают как бы не проиграть, а еще лучше выиграть. По крайней мере должны так думать.
Хирург во время операции не думает, как бы не обидеть медсестру - он думает, как бы не убить пациента, а еще лучше спасти его.
Пилот самолета на взлете, посадке или в другой стрессовой ситуации не думает о том, как работается стюардессам - его главная забота, что бы все закончилось хорошо, без потерь, ущерба и травм или смертей.
И так далее.
Нужно фокусироваться на важном; а субъективный комфорт коллег программиста совершено не важен.

Only registered users can participate in poll. Log in, please.
Согласны с этим мнением?
14.46% Полностью согласны.104
23.37% Совершенно несогласны168
25.03% По большой части верно.180
37.13% Есть здравые мысли, но мало.267
719 users voted. 97 users abstained.
Tags:
Hubs:
Total votes 123: ↑62 and ↓61+1
Comments364

Articles