Тема идеального кода нередко вызывает полемику в среде матерых программистов. Тем интереснее было заполучить мнение директора по разработке Parallels RAS Игоря Марната. Под катом его авторский взгляд по заявленной теме. Enjoy!



В качестве введения хотелось бы остановиться на вопросе, почему же я вообще решил написать эту небольшую статью. Перед ее написанием я задал вопрос из заголовка нескольким разработчикам. С большинством из ребят довелось поработать больше пяти лет, с некоторыми чуть меньше, но их профессионализму и опыту доверяю безоговорочно. У всех стаж промышленной разработки больше десяти лет, все работают в российских и  международных компаниях, производителях программного обеспечения. 
 
Некоторые коллеги затруднились с ответом (кое-кто думает до сих пор), другие назвали один-два примера сразу. Тем, кто привёл примеры, я задал уточняющий вопрос – «Что, собственно, вызвало это восхищение?». Ответы соответствовали результатам следующего этапа моего небольшого исследования. Я поискал в сети ответы на этот вопрос в разных формулировках, близких к заголовку статьи. Все статьи отвечали примерно таким же образом, каким ответили мои товарищи.
 
Ответы разработчиков, как и формулировки найденных статей, относились к читаемости и структуре кода, изяществу логических конструкций, использованию всех возможностей современных языков программирования и следованию определённому стилю оформления.
 
Когда же я задал вопрос о «божественном коде» себе, то ответ всплыл незамедлительно, из подсознания. Я сразу же подумал о двух примерах кода, с которыми работал давно (больше десяти лет назад), но чувство восхищения и некоторого благоговения испытываю до сих пор. Обдумав причины восхищения каждым из них, я сформулировал несколько критериев, о которых речь пойдёт ниже. На первом примере я остановлюсь вскользь, второй же хотелось бы разобрать более подробно. Кстати, в разной степени, все эти критерии рассмотрены в настольной книге каждого разработчика «Совершенный код» от Стива Макконнелла, но эта статья заметно короче.

Пример из 90-х


Первый пример, о котором я упомяну, относится к реализации модемного протокола v42bis. Этот протокол разработан в конце 80-х — начале 90-х годов. Интересная идея, воплощенная разработчиками протокола — реализация поточного сжатия информации при передаче по нестабильной (телефонной) линии связи. Отличие поточного сжатия от сжатия файлов фундаментальное. При сжатии файлов архиватор имеет возможность проанализировать набор данных полностью, определить оптимальный подход к сжатию и кодированию данных, и записать данные в файл целиком, не беспокоясь о возможных потерях данных и метаданных. При разархивации, в свою очередь, набор данных снова доступен полностью, целостность обеспечена контрольной суммой. При поточном же сжатии архиватору доступно только небольшое окно данных, нет гарантии отсутствия потери данных, необходимость переустановки соединения и инициализации процесса сжатия является обычным делом.

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

Linux всему голова!


Второй пример совершенного кода хотелось бы разобрать подробнее. Это код ядра Linux. Код, который на момент написания статьи управляет работой 500 суперкомпьютеров из top 500, код, который работает в каждом втором телефоне в мире и который управляет большей частью серверов в сети Интернет.
 
Рассмотрим для примера файл  memory.c из ядра Linux, который относится к подсистеме управления памятью. 

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

static void tlb_remove_table_one(void *table)
{
        /*
         * This isn't an RCU grace period and hence the page-tables cannot be
         * assumed to be actually RCU-freed.
         *
         * It is however sufficient for software page-table walkers that rely on
         * IRQ disabling. See the comment near struct mmu_table_batch.
         */
        smp_call_function(tlb_remove_table_smp_sync, NULL, 1);
        __tlb_remove_table(table);
}


2. Комментариев в коде не слишком много, но те, что есть, обычно полезны. Они, как правило, описывают не действие, которое и так очевидно из кода (классический пример бесполезного комментария — “cnt++; // increment counter”), а контекст этого действия — почему здесь делается то, что делается, почему это делается так, почему здесь, с какими предположениями это используется, с какими другими местами в коде это связано. К примеру: 

/**
 * tlb_gather_mmu - initialize an mmu_gather structure for page-table tear-down
 * @tlb: the mmu_gather structure to initialize
 * @mm: the mm_struct of the target address space
 * @start: start of the region that will be removed from the page-table
 * @end: end of the region that will be removed from the page-table
 *
 * Called to initialize an (on-stack) mmu_gather structure for page-table
 * tear-down from @mm. The @start and @end are set to 0 and -1
 * respectively when @mm is without users and we're going to destroy
 * the full address space (exit/execve).
 */
void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm,
                        unsigned long start, unsigned long end)


Другой вид использования комментариев в ядре — описание истории изменений, обычно в начале файла. История ядра насчитывает уже почти тридцать лет, и некоторые места читать просто интересно, чувствуешь себя сопричастным истории: 

/*
 * demand-loading started 01.12.91 - seems it is high on the list of
 * things wanted, and it should be easy to implement. - Linus
 */
 
/*
 * Ok, demand-loading was easy, shared pages a little bit tricker. Shared
 * pages started 02.12.91, seems to work. - Linus.
 *
 * Tested sharing by executing about 30 /bin/sh: under the old kernel it
 * would have taken more than the 6M I have free, but it worked well as
 * far as I could see.
 *
 * Also corrected some "invalidate()"s - I wasn't doing enough of them.
 */


3. В коде ядра используются специальные макросы для проверки данных. Они также используются для проверки контекста, в котором работает код. Функциональность этих макросов похожа на стандартный assert, c той разницей, что разработчик может переопределить действие, которое выполняется в случае истинности условия. Общий подход к обработке данных в ядре — проверяется всё, что приходит из user space, в случае ошибочных данных возвращается соответствующее значение. При этом может использоваться WARN_ON для выдачи записи в лог ядра. BUG_ON же обычно весьма полезны при отладке нового кода и запуске ядра на новых архитектурах. 
 
Макрос BUG_ON обычно вызывает печать содержимого регистров и стека и либо останавливает всю систему, либо процесс, в контексте которого произошёл соответствующий вызов. Макрос WARN_ON  просто выдаёт сообщение в лог ядра в случае истинности условия. Есть также макросы WARN_ON_ONCE и ряд других, функциональность которых понятна из названия.

void unmap_page_range(struct mmu_gather *tlb,
….
         unsigned long next;
 
        BUG_ON(addr >= end);
        tlb_start_vma(tlb, vma);
 
 
int apply_to_page_range(struct mm_struct *mm, unsigned long addr,
…
        unsigned long end = addr + size;
        int err;
 
        if (WARN_ON(addr >= end))
                return -EINVAL;


Подход, при котором данные, полученные из ненадежных источников, проверяются перед использованием, и реакция системы на “невозможные” ситуации предусмотрена и определена, заметно упрощает отладку системы и её эксплуатацию. Можно рассматривать этот подход как реализацию принципа fail early and loudly. 

4. Все основные компоненты ядра предоставляют пользователям информацию о своём состоянии через простой интерфейс, виртуальную файловую систему /proc/.

К примеру, информация о состоянии памяти доступна в файле /proc/meminfo

user@parallels-vm:/home/user$ cat /proc/meminfo
MemTotal:        2041480 kB
MemFree:           65508 kB
MemAvailable:     187600 kB
Buffers:           14040 kB
Cached:           246260 kB
SwapCached:        19688 kB
Active:          1348656 kB
Inactive:         477244 kB
Active(anon):    1201124 kB
Inactive(anon):   387600 kB
Active(file):     147532 kB
Inactive(file):    89644 kB
….


Информация, приведённая выше, собирается и обрабатывается в нескольких исходных файлах подсистемы управления памятью. Так, первое поле MemTotal, это значение поля totalram структуры sysinfo, которая заполняется функцией si_meminfo файла page_alloc.c.
 
Очевидно, организация сбора, хранения и предоставления пользователю доступа к такой информации требует усилий от разработчика и некоторых накладных расходов от системы. При этом, польза от наличия удобного и простого доступа к таким данным неоценима, как в процессе разработки, так и эксплуатации кода. 
 
Разработку практически любой системы стоит начинать с системы сбора и предоставления информации о внутреннем состоянии вашего кода и данных. Это очень поможет в процессе разработки и тестирования, и, в дальнейшем, в эксплуатации.

Как сказал Линус, «Bad programmers worry about the code. Good programmers worry about data structures and their relationships».

5. Весь код читается и обсуждается несколькими разработчиками перед коммитом. История изменений исходного кода записана и доступна. Изменения любой строки можно проследить вплоть до её возникновения — что менялось, кем, когда, почему, какие вопросы при этом обсуждались разработчиками. К примеру, изменение https://github.com/torvalds/linux/commit/1b2de5d039c883c9d44ae5b2b6eca4ff9bd82dac#diff-983ac52fa16631c1e1dfa28fc593d2ef в коде memory.c, вдохновлено  багом  https://bugzilla.kernel.org/show_bug.cgi?id=200447 в котором сделана небольшая оптимизация кода (вызов включения защиты памяти от записи не происходит, если память уже защищена от записи). 
 
Разработчику, работающему с кодом, всегда важно понимать контекст вокруг этого кода, с какими предположениями код создавался, что и когда менялось, чтобы понимать, какие сценарии могут быть затронуты теми изменениями, который собирается делать он сам.

6. Все важные элементы жизненного цикла кода ядра документированы и доступны, начиная со стиля кодирования и заканчивая содержимым и расписанием выпуска стабильных версий ядра. Каждый разработчик и пользователь, который хочет работать с кодом ядра в том или ином качестве, имеет для этого всю необходимую информацию.
 
Эти моменты показались мне важными, в основном, они определили моё восторженное отношение к коду ядра. Очевидно, список весьма краткий и может быть расширен. Но перечисленные выше пункты, на мой взгляд, относятся к ключевым аспектам жизненного цикла любого исходного кода с точки зрения разработчика, работающего с этим кодом. 
 
Что бы мне хотелось сказать в заключение. Разработчики ядра — умные и опытные, они добились успеха. Доказано миллиардами устройств под управлением Linux
 
Будьте как разработчики ядра, используйте лучшие практики и читайте Code Complete!

З.Ы. Кстати, а какие критерии идеального кода лично у вас? Поделитесь мыслями в комментариях. 

Комментарии (51)


  1. AvkPiterskij
    17.10.2018 12:30

    И все же, о чем тут. Снова про комментарии, большие и маленькие буквы, суффиксы и префиксы в именах? Ставить ли точку с запятой, если это не обязательно? Глобальные переменные — хорошо это или нет?


    1. ganqqwerty
      17.10.2018 16:26

      Про что-то посложнее — уже надо думать начинать, прежде чем писать вот например линусовый пример про if с полпинка не родишь.


  1. dipsy
    17.10.2018 13:27
    +1

    int apply_to_page_range(struct mm_struct *mm, unsigned long addr,
    …
            unsigned long end = addr + size;
            int err;
     
            if (WARN_ON(addr >= end))
                    return -EINVAL;

    Я бы больше типизации добавил для наглядности, да и во избежание множества проблем при развитии продукта и ошибок от невнимательности (хотя при 100% тестовом покрытии второе не так критично)
    result_t apply_to_page_range(struct mm_struct *mm, addr_t addr,
    …
            addr_t end = addr + size;
            result_t err;
     
            if (WARN_ON(addr >= end))
                    return -EINVAL;


    1. DistortNeo
      17.10.2018 22:24

      Слабая типизация C/C++ не очень спасёт от ошибок от невнимательности.


      1. dipsy
        18.10.2018 05:57

        Ну какая-никакая, а даже enum иногда спасает, в отличие от int или даже unsigned long.


        1. DistortNeo
          18.10.2018 11:48

          Только если enum class, а не просто enum.


  1. danfe
    17.10.2018 13:33

    Второй пример совершенного кода хотелось бы разобрать подробнее. Это код ядра Linux.
    Простите, но качество кода Linux весьма далеко от совершенного; приводить его в пример я бы точно не стал. Там неплохой код для промышленного проекта такого масштаба и круга разработчиков (включая множество компаний, в которых код драйверов и ядра пишут разномастные программисты на зарплате с различными отношением к своей работе и представлением о прекрасном), но не более того. Вот смотришь, например, на дифф (это фикс CVE-2017-14497) и видишь, что как был до фикса говнокод, таким после фикса и остался.

    Как-то в одном интервью Костя Белоусов, один из ведущих разработчиков FreeBSD, хорошо сказал про код Linux:
    После этого интересного опыта я уже даже подумывал бросить аспирантуру, и возможность “покрутить код” под Unix-like-системы выглядела интересно.

    Но чтение кода Linux’ а было мне неприятно: я бы так не писал.


    1. Hilbert
      17.10.2018 15:00
      +1

      Вот только дальше он пишет:

      Я уже не помню детали, и в Linux’ е тот код, который вызвал у меня такое резкое неприятие, наверняка переписан уже несколько раз – нужно помнить, что это все происходило 12–15 лет тому назад.


      1. danfe
        17.10.2018 15:23

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


        1. SmirkinDA Автор
          17.10.2018 16:00

          Попросил Игоря ответить на вопрос. Вот:

          Это вопросы масштаба. Как и любой другой код, код ядра содержит уязвимости разного кода. Уязвимости в коде не находят тогда, когда им не пользуются. Как и код Windows, код ядра распространен настолько широко и используется в настолько разных сценариях, что баги и уязвимости в нём находятся часто. Как сказал Глеб Жеглов, «Правопорядок в стране определяется не наличием воров, а умением властей их обезвреживать!».

          Код ядра, подход коммюнити к разработке, подход к релизам (http://kroah.com/log/blog/2018/02/05/linux-kernel-release-model/) предоставляют всё, что нужно разработчикам, чтобы минимизировать количество подобных проблем.


          1. danfe
            17.10.2018 16:59

            Бог с ним, с количеством уязвимостей, это как раз хорошо ложится на «не находят тогда, когда не пользуются» — Linux популярное ядро, всё логично. У меня больше претензий к самим уязвимостям и закрывающим их патчам, и вот тут к «умению обезвреживать» возникают вопросы. Пять уязвимостей в реализации сокетов AF_PACKET за год — ну куда это годится? Справедливости ради, во фре такое тоже бывало: так, в своё время procfs(5) отключили по умолчанию из-за long history of security vulnerabilities and other problems, но в общем и целом, когда смотришь на фрёвые фиксы, почти всегда сразу понятно, в чем была проблема и как была пофикшена. В случае Linux зачастую приходится сидеть и раскуривать код (мне этим часто приходится заниматься, т.к. мы вынуждены поддерживать несколько версий ядра из разных веток, и вручную бэкпортить фиксы; постоянные изменения форматирования и переименования каких-нибудь констант, из-за чего оригинальный патч не прикладывается, — это отдельная грустная песня).


    1. SmirkinDA Автор
      17.10.2018 15:52

      Попросил Игоря ответить.

      Вот:

      Качество кода — понятие субъективное, как я упомянул в начале статьи, у каждого разработчика свои примеры. Лично мне код ядра нравится и работать с ним было приятно, но дело не в этом. Этот код не идеален. Но это код, над которым работает, наверное, одно из самых больших и активных сообществ разработчиков. И это код, который, думаю, работает на наибольшем количестве устройств в мире. Я не могу с лету придумать другой пример кода, который работал бы в данный момент времени на сопоставимом количестве процессоров (я имею в виду процессоров в целом, всего, в мире), как код ядра Линукс. Это говорят о качестве кода и его сопровождаемости — плохой, некачественный код, который тяжело сопровождать, не получил бы такого распространения.


      1. Ad_xname
        18.10.2018 13:03

        Это абсолютно не связанные вещи. Одно дело — какая либо библиотека, которая используется непосредственно программистами.
        И другое дело — операционная система, которая «выстрелила», поскольку позволила закрыть целую нишу в системном ПО. Решения об использовании системы принимают все же не разработчики.


        1. DGolubets
          18.10.2018 17:36

          Я согласен с тем что код мог бы быть лучше, по стандартам 2018.

          Но и как с какой-нибудь библиотекой — тут вопрос приоритетов:
          1. она должна работать без багов,
          2. она должна работать быстро
          3. она должна быть хорошо написана

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

          Так и с линуксом — я в его код не лезу и пока он развивается и работает — меня все устраивает.

          Во многих компаниях разработчики вполне себе принимают решения какую систему использовать. Бизнесу зачастую не важно что там в технологическом стеке.


          1. Ad_xname
            18.10.2018 18:31

            Чем больше людей участвует в разработке, тестировании и эксплуатации, тем больше вероятность того, что баги будут выявлены. Чисто математически.
            Быстро — это понятие относительное. С чем сравнивать? На каких задачах смотреть?
            А вот хорошо написано — это не важно для ОС, но важно для библиотеки. Поскольку у библиотеки должен быть удобный и понятный интерфейс, грамотно выстроеная логика и удобство просмотра исходников (полностью или частично) сторонними программистами.
            Хороший код — это не бизнес требование для ОС. Поэтому разработчики не должны давать советы, основанные на этом критерии бизнес-пользователям.
            У Linux есть важные преимущества с точки зрения бизнеса, именно они обычно и влияют на принятие решения.


            1. DGolubets
              18.10.2018 19:56

              Хороший код — это не бизнес требование для ОС

              С этим не поспоришь. Я имел ввиду выбор ОС как таковой. Например, под линукс проще собирать опен-сорс код, а под windows — головная боль, а многие проекты и не заработают вообще. Это уже аргумент бизнесу.

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

              Насчет быстро (хотя лучше наверное эффективно, ведь надо брать в рассчет расход системных ресурсов тоже) — нужно рассматривать конкретную решаемую задачу. Например если библиотека читает данные из кафки, я ожидаю при среднем размере записи упереться в скорость сетевой карты. Если я вдруг не могу этого сделать и эта библиотека ест мой CPU или создает сотню потоков — что-то не так, правильно? Если явных косяков не заметно, то надо сравнивать с конкурентами. Если большой разницы нет (естественно гнаться за долями процента в ущерб коду не стоит) — то переходим к качеству кода. Если есть — оптимизируем.


              1. stul5tul
                18.10.2018 20:12

                Например, под линукс проще собирать опен-сорс код, а под windows — головная боль, а многие проекты и не заработают вообще.

                Вы про проекты, изначально заточенные под Unix?


                1. DGolubets
                  19.10.2018 12:59

                  Да. Но, это не всегда очевидно. Одна библиотека тянет за собой кучу зависимостей и среди них может оказаться одна, заточенная под nix.

                  Я не раз сталкивался с багами под вин из-за того что разработчики банально не тестировали под нее (например рассчитывали только на линуксовые разделители файлов) и не собирались этого делать в будущем. По началу я думал: «ок, исправлю, сделаю пулл реквест», но потом надоело — зачем плыть против теченья?

                  Но это сервера и бэкенд. Наверное если бы я занимался каким-нибудь gamedev, то наоборот нужна была бы win.


                  1. stul5tul
                    19.10.2018 14:27

                    (например рассчитывали только на линуксовые разделители файлов)


                    Под Windows есть специальные системы сборки типа Mingw/Cygwin, способные автоматически учитывать, то, что разработчики не учли подобные вещи. И даже автоматически учитывать куда как более серьезные отличия.


  1. nmrulin
    17.10.2018 15:48

    Вообще не очень то хороший пример «совершенного кода»
    static void tlb_remove_table_one(void *table)
    Т.к. из одной функции просто вызывается другая. Ещё надо долго покликивать, чтобы понять что она всё-таки делает(если человек до этого не знает).
    «Макрос BUG_ON обычно вызывает печать содержимого регистров» — но в коментариях к коду это не прописано. Может в данном случае он делает то, что «необычно».


  1. Taliesien
    17.10.2018 15:48
    +1

    Лично у меня вызывает восхищение мой код, написанный мной более года назад. И восхищении из серии: «Тот кто это написал — сказочный д… б».


    1. nmrulin
      17.10.2018 15:49

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


  1. Heian
    17.10.2018 15:56

    Я выделяю три основных признака. Перечислю в порядке убывания значимости лично для меня.
    — Красив визуально и эстетически, вне зависимости от языка. На него просто приятно смотреть!
    — Красив алгоритмически. Решения и паттерны, используемые в коде, не должны быть корявыми, не должно быть разбитых окон.
    — Лаконичен. Чем меньше комментариев — тем лучше, код должен стремиться к самодокументированию и емкости.


    1. HenadziMatuts
      17.10.2018 17:19

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


    1. AlexSky
      17.10.2018 20:52

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


      1. DelphiCowboy
        18.10.2018 05:51

        Я бы пожертвовал алгоритмической красотой в пользу простоты и понятности.

        А если нужна скорость?
        Самая простая и понятная сортировка — это пузырьком, а быстрая сортировка — куда замороченнее.


        1. stul5tul
          18.10.2018 06:08

          А если нужна скорость?

          Ну а если нужна, то не пожертвовал бы. Это же очевидно.

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

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

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


        1. DGolubets
          18.10.2018 17:47

          Какая-то путаница в красивых и понятных алоритмах.

          Для меня быстрая сортировка — одновременно красивый и понятный алгоритм.
          Как иначе можно оценить красоту алгоритма, не поняв его?

          Вот оптимизированный код\алгоритм может быть сложнее и менее изящен.


        1. Antervis
          18.10.2018 19:16

          пример с сортировкой плоховат, однако. Всё-таки видя функцию с названием sort* догадаться, что она делает, несложно. А внутри можно подписать в комментарии какой алгоритм эта версия сортировки реализует и дальше читаемость реализации практически не имеет значения.

          Вообще, это частный случай правила «не используй в одном блоке разные уровни абстракций»


  1. BalinTomsk
    17.10.2018 16:21

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

    Отсутствие результата функции — как следствие не тестируемость — использование void — это плохо.

    И еще несколько вещей делающих приведенные образцы антипримером.


    1. HenadziMatuts
      17.10.2018 17:30
      +1

      Насчёт проверки на NULL сказано в статье — обязательно проверяются данные которые приходят из пользовательского пространства. После того как были проведены внешние проверки — проводить их повторно во внутренних функциях часто не имеет смысла. И в приведённом контексте с void тоже нет никаких проблем.


      1. BalinTomsk
        17.10.2018 22:23
        -1

        ----обязательно проверяются данные которые приходят из пользовательского пространства.

        А если ее заходят вызвать оттуда, где не проверяют?

        Ее проверяют прямо перед вызовом или там есть еще пару строк кода, внутри которых могут изменить адрес функции?

        А если это сделают через пару лет, когда забудут что она внутри не проверяются?

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

        В функции, имеющую одну строку тоже нету смысла — зачем ее иметь? Вызывайте внутреннюю функцию из внешней или сделайте ее макросом.

        Отсутствие результата у функции — это принт?

        Если нет как вы собрались ее тестировать?

        Приведенные образцы кода будут забракованы в любой более-менее приличной компании с mission critical кодом.


        1. HenadziMatuts
          18.10.2018 08:38

          Первая часть твоего комментария — это параноя, причём в самом отрицательном смысле. «А если захотят» — не захотят, «А если сделают» — нет, не сделают: есть экспортируемые функции, а есть внутренние, если программист использует внутренние функции, то он знает, что делает и он знает свои данные.

          Однострочники в ядре — это часто обёртки над более общими вещами, а макросы не всегда уместны.

          И далеко не всё можно тестировать, даже не так, далеко не всё нужно тестировать. Это тоже параноя.

          Эти образцы могут быть забракованы только если рассматривать их вне контекста в котором они используются. И то, для этого нужно быть двинутым на всяких code complete настолько, что бы воспринимать их как обязательные к исполнению законы, а не как рекомендации.


          1. BalinTomsk
            18.10.2018 16:46

            Эта параноя, порой даже летает на самолетах.
            Я вам привожу аргументы из практики компаний Forbs-100 где я обычно работаю.

            Можно конечно заниматся абстрактным исскуством, только это обычно интересует 2.5 человека в мире и не практично.

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


            1. HenadziMatuts
              18.10.2018 17:39

              Ну, всяким разным оупенсорсом и конкретно кодом ядра Linux интересуются чуть больше чем 2.5 человека, несмотря на мнение, что он ужасен. А минусы, увы, не мои.


  1. Ermit
    17.10.2018 16:38

    Только тогда, когда естественный язык придет в программирование, мы станем читать классы и модули как рассказы и присуждать литературные премии лучшим программистам. :)


    1. fireSparrow
      17.10.2018 17:20

      >> когда естественный язык придет в программирование
      Не дай бог. Мне с лихвой хватает уже того, что задачи ставят на естественном языке. Если мы ещё и код начнём на нём писать, то человечество обречено.


      1. Ermit
        17.10.2018 17:25

        Пайтон уже очень похож на controlled NL. И, во-многом, поэтому любим. И, да, я должен Вас огорчить, лет через пять-десять миллиардов наступит тепловая смерть вселенной, мы обречены…
        Честно скажу, не вижу разницы между естественным языком и языком программирование, если хорошо владеть и тем и другим…


        1. dipsy
          17.10.2018 20:22

          лет через пять-десять миллиардов наступит тепловая смерть вселенной
          Нет! Через пару-тройку млрд лет Солнце гарантированно уничтожит Землю, но Вселенная просуществует до тепловой смерти ещё очень долго, на порядки больше. Правда не надо забывать вероятность большого разрыва или фазового перехода вакуума, что может случиться ещё раньше, если ускоренное расширение продолжится теми же темпами.


          1. j-ker
            18.10.2018 03:27

            Да полноте… мы не знаем что будет на самом деле. Вы перечислили ГИПОТЕЗЫ, которые вовсе не являются 100% сценариями развития событий в проекции, известной нам по локальному световому конусу 4-мерного пространства-времени


        1. DelphiCowboy
          18.10.2018 09:45

          лет через пять-десять миллиардов наступит тепловая смерть вселенной

          А чё так нереально быстро?!
          Тепловая смерть — это то когда погаснут все звёзды.
          А через пять, или даже десять миллиардов лет всё ещё будет материал для появления новых звёзд.
          — хорошая новость: материала для новых звёзд хватит как минимум ещё на триллион лет,
          — плохая новость: Большой Разрыв ожидается гораздо раньше — уже через 80 миллиардов лет, согласно Итану habr.com/post/369327 (у Итана оценка 2014 года, в Википедии оценка 2011 года, согласно которой ожидалось через 22 миллиарда лет)
          image


  1. red_andr
    17.10.2018 16:43
    +1

    Вижу много критики в комментариях. Возможно соглашусь отчасти. Хотя меня интересует другое. Почему никто из комментаторов сам не приводит примеры хорошего кода? Это же гораздо полезнее. Критиковать то проще всего.


    1. nmrulin
      17.10.2018 16:55

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


    1. BD9
      17.10.2018 18:54

      «Критиковать то проще всего.» — нет, хорошая критика требует большой работы.


    1. AvkPiterskij
      17.10.2018 19:43

      void foo(void)
      {
      return;
      }
      Ни одной ошибки! Шутка но отчасти. Из комментариев видно, что у каждого свои представления о прекрасном, спорить можно, переубеждать не стоит. Вы в статье попытались провести анализ темы, и это видно, я лично именно это оценил. От себя добавлю, я научился читать программы, где то лет через 5-6, как стал получать деньги за то что их пишу. Может я ошибаюсь, но думаю у многих было так же. Вспомните «мама мыла раму».


  1. BD9
    17.10.2018 18:27
    +1

    Чем лучше код, тем он реже применяется, т.к. понять его могут немногие.
    Или бывает: код очень хорош, но выглядит просто, и его считают простым, хотя это не так. При попытках изменения кода всё ломается, и другие говорят, что «вот, плохой код был!». Хоть это и не так.
    У Linux — точно несовершенный код. Хотя бы из-за размеров и скорости разработки таковым его сделать не удастся.
    Мой пример: код компилятора (или переводчика?) языка Eiffel от Бертрана Мейера. Обычно язык ограничен в средствах, т.к. создатель его смог написать только такой компилятор. У Eiffel же всё по максимуму.


    1. nmrulin
      19.10.2018 10:58

      «и другие говорят, что «вот, плохой код был!»» — и правильно. Хороший код это когда быстро можно понять, что он делает(если раньше его не разу не видел) и быстро внести изменения.


  1. stul5tul
    17.10.2018 21:47

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


    Это говорит о нетребовательности к ядру ОС в различных системах. Какая по сути разница что там? Linux, FreeBSD или Mach? Ну работает и работает, свои функции выполняет.

    Просто проще поддерживать обвязку всего прочего программного обеспечения с одним ядром ОС. Без относительно качества этого ядра.

    Linux используется везде — просто потому, что нужно хоть что-то использовать. Linux оказался в свое время, в своем месте, с достаточно активными продвиженцами его — и только. Никакого сверхкачества ядро Linux не имеет (да хоть по сравнению с выше упомянутыми).

    Что по «качеству» ядра Linux — то просто посмотрите, что значит действительно мгновенная загрузка безо всяких там SSD и standby-in-memory на примере ОС Haiku.

    Но нет, человечеству нужно не это. Человечеству нужно снижение себестоимости создания программного обеспечения. Пусть даже это программное обеспечение и будет более медленным.


  1. skptricks
    18.10.2018 07:29

  1. vsb
    18.10.2018 12:40

    Очень нравится код Spring Framework. Пожалуй это лучший Java-код, который я видел. С одной стороны его просто приятно читать, всё консистентно, правильно названо, функции правильного размера. С другой стороны множество паттернов проектирования, позволяющих красиво и без хаков решать практически любую возникающую задачу. С третьей стороны это множество всё же не делает код нечитаемым, всегда, когда нужно было разобраться, у меня это получалось. Мой главный принцип при изучении кода это control+click (перейти на реализацию функции) и со Spring-ом это отлично работает. С четвёртой стороны все публичные API хорошо задокументированы и сверху приложен независимый User Guide, довольно полный и хорошо написанный.

    Ещё могу сказать, что часто нравится не сам код, а дизайн чего-либо, относящегося к программированию. Например мне нравился дизайн языка Java до версии 5. Он был довольно минимален и приятен. К сожалению с 5 версии пошло развитие не в ту сторону, как по-мне, ну да ладно, в общем красивым он быть перестал. Правда стандартная библиотека Java во многих местах это каноничный пример того, как делать не нужно. Иногда мне хочется взять 4-ю джаву, форкнуть и переписать её стандартную библиотеку. Думаю, даже на современных JVM это будет работать. Может быть даже кому-то понравится, пишут же на Go люди.

    Так вот, в этом плане мне очень нравится Rust. Хотя может я его плохо знаю, но то, что я знаю, мне прям очень нравится. Часто возникает такое ощущение, что вот это я бы сделал так же и лучше сделать уже невозможно. Ещё Kotlin вызывал похожие ощущения.


  1. Gritsuk
    20.10.2018 06:24

    Мне кажется, сначала нужно определить, что считать хорошим кодом. Быстрый? Понятный? Лаконичный?


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


    Другими словами — прежде чем оптимизировать, давайте определимся с оптимизируемым параметром:)