Лекция 5. Дисциплина оформления и ведения исходного текста

О чём мы успели поговорить. Начали с момента — давайте создадим окружение для разработки. Как вообще идёт сборка проекта. Как устроена сборка более сложных проектов. Потом народился разговор об отладке, потому что выяснилось, что большие программы надо трассировать и отлаживать. Потом наш проект вырос до того, что потребовал совместной разработки и версионирования и мы поговорили про работу с исходным текстом разными способами. Следующий шаг естественный — если команда работает над кодом и есть более опытные, то остальные к ним прислушиваются. Соответственно, когда люди пишут патч, они показывают более опытному, и тот либо принимает, либо не принимает и либо объясняет почему, либо нет, и пишет сам. За последние 20 лет тирании стало поменьше, объяснений побольше. При совместной разработке возникает желание научить и научиться писать код правильно и так, чтобы друг друг не мешать. Хотя в некотором плане это аукается на индивидуальную разработку, чтобы понимать, что было написано ранее, например.е

  1. Дисциплина совместного использования хранилища (особенно в централизованном хранилище).
  2. Дисциплина оформления и ведения исходного кода.

Дисциплина работы с хранилищем

Что касается хранилища, то в случае использования децентрализованной схв с этой дисциплиной наступает цыганская вольница. Но, простейшие вещи на поверхности:

  1. Одно изменение — один коммит.
  2. Двухчастный commit message. Есть два противоречивых требования к коммиту: описать все изменения и сделать это в одну строчку. Вы могли исправить несколько частей кода для решения одной проблемы. Соответственно, коммит месседж делится на резюме и подробности. В гите это уже чуть ли не стандарт.
  3. Что касается самого процесса разработки и создания веток, то дисциплин чёртова прорва, но одну вещь надо отметить — трёхпоточную разработку. Трёхпоточный режим разработки состоит из трёх веток: development (в которой идёт разработка), testing (ветка для тестирования того, что должно стать релизом) и release (ветка релиза с допиливанием багов обнаруженных после релиза). Называться они могут по разному, но это не важно. Речь идёт о том, что есть то место, куда вы без зазрения совести добавляете изменения, то место, где вы добиваетесь совершенства, и то место, где вы уже добились совершенства.
  4. Когда ваш проект достаточно большой нужно устраивать дисциплину не только личного хранилища, но и мёрджа. Граф слияний. Есть некая вершина пирамиды, куда отвественный подтягивает изменения, подготовленные лейтенантами, каждый из которых отвечает за свою подсистему и подтягивает изменения из других мест (не факт, что разных). Обратите внимание, что на этой картинке у нас рисуется ориентированный граф с некоторыми свойствами: у него нет циклов и у него есть уровни. Этой картинки стоит придерживаться, если вы хотите получить качественный продукт.

Дисциплина оформления исходного кода

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

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

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

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

Что такое эта продуктивность? Из чего она состоит?

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

  2. Включение в общее информационное пространство.
    Начинаем думать не только о себе. Ваш код будут читать другие. Документирование — стороннее и внутри кода в виде комментариев. Всевозможные операции, связанные с code reuse, когда все делают действия, чтобы другие могли пользоваться кодом — библиотеки и т. д.

  3. Доступность сообществу.
    Когда речь идёт уже о достаточно большом сообществе, код надо писать с оглядкой на то, что его будут читать и модифицировать другие люди.

    1. KISS — Keep It Simple Stupid.
      Тупой код надо писать не потому что другие люди тупые. Наоборот, они могут быть очень острыми. Пересечение знаний гениев может быть не большим, и надо в него поместиться.

    2. Соблюдение правил.
      Если у сообщества есть какие-то правила — их надо соблюдать. Сообщество вправе ожидать от вас, а вы от сообщества соблюдения этих правил. То есть для достижения общей цели вы согласны несколько ограничить свою цыганскую вольницу. В свободном сообществе единственный способ заявить о себе это написать код, и можно это делать либо по правилам, либо нет, декларируя о себе сообщесву разные вещи. «Добровольное использование разумных правил позволяет надеяться на то, что все будут придерживаться этих правил». В этом плане свободное сообщество очень близко по своей сути к ницшеанской морали. Вы поддерживаете такие законы, про которые известно, что и все остальные будут их поддерживать.

  4. Учёт специфики проекта.

    1. Размер и ротация сообщества.
    2. Требования к надежности кода.
      Надо хорошо себе представлять, что, условно говоря, любой код ненадёжен. И как следствие: комплекс мероприятий по улучшению надёжности кода может быть бесконечно большим. И требовать бесконечных ресурсов. Где остановиться зависит от того, каковы ваши реальные потребности. Если вы почитаете правила написания драйверов к современному Линукс-ядру, забыв что это Линукс-ядро, вам многие веши покажутся оверкиллом. Но для ядра они имеют смысл.

    3. Специфика языка программирования
    4. Специфика предметной области (например, программирование драйверов с магическими числами).

Давайте теперь распилим это требование к продуктивности в другой плоскости. Что имеет смысл проверять на предмет того, надо это вносить в coding style policy или не стоит? Что имеет смысл фиксировать или не фиксировать?

  1. Внешний вид кода. Отступы, соглашения о переносе длинных строк и т. д.

  2. Именование. Венгерская нотация. Человек, который её придумал [Чарльз Симони] не думал хранить в названии переменной её тип, глупее этого только хранить в имени значение. Он пытался внести в имя переменной семантику использования. Но большинство индус-триальных программистов не понимали, что такое семантика, и писали туда тип.

  3. Не использование особенностей языка программирования.

  4. Комментарии.

Приехала программа к компании, которая её поддерживает не первый год. Программа падает. Выясняют, что падает при выводе непонятного байта в непонятный порт железяки. И в этой строчке есть такой комментарий ;JSB RIP*/ Начинают искать автора. За несколько месяцев находят. Спрашивают его, что это значит. Тот говорит, что не помнит. «А, помню! Обратите внимание на тот байт, который передаётся в порт. Если перевести его в десятичный вид, то получится год смерти Иоганна Себастьяна Баха. Мне пришло в голову такое замечательное совпадение и я его зафиксировал». Плохой метод регулировки комментариев -- процентное соотношение кода и комментариев. Плохо тем, что люди начинают писать генератор комментариев -- перед каждым циклом писать "Это цикл по переменной и". Не руками. Получаем прекрасный код с заданным процентом комментариев.

  1. Разбиение кода на модули.
    Например, что касается функций, если она здоровая и решает сразу несколько задач... В таком случае никакого code reuse не получится.

  2. Повторное использование.
    Вы пишите программу не один. Скорее всего архитектор уже подумал, что много функций написано. Не надо писать их самому. Если пришло в голову изобрести устройство для быстрого перемешения по ровным поверхностям — два колеса, палка, седло — возможно оно уже придумано. Даже если вы название уже придумали, ну, скажем, «убыстритель ног», то вполне возможно, что оно уже существует. В Qt, например, все подзадачи уже решены. Я уже не говорю про MSDN, где есть в несколько раз больше, чем всё. Надо только уметь по нему искать.

Обратное тоже верно. Если вы пишите что-то общепотребное, подумайте о том, что это будут использовать многие.

Linux Kernel coding style

Рассмотрим в качестве примера уже упоминавшийся Linux Kernel сoding style.

Я готов подписаться под каждым словом и поведать всему миру и всем программистам на Си, что счастье есть. Всем искренне рекомендую прочитать этот документ. Во-первых, он читается как литературное произведение. Во-вторых, содержит много полезных рекомендаций. Первый совет там — распечатать и сжечь копию GNU Coding Style Guide. Хотя там достаточно серьезная специфика, но это приличный позитивный документ, который стоит прочесть.

  1. Отступы делаются только табуляциями шириной 8 пробелов, общая ширина строки не больше заданной. На Java, например, это было бы слабо возможно. Программистам на Джаве, которым трудно соблюсти эти требования, стоит понять, что это всё-таки не Java, а C, и что этот код будут читать сотни людей.
  2. Отдельный разговор про то, как ломать длинные строчки.
  3. Как раставлять пробелы и скобки. К каждому пункту прилагается объяснение, почему рекомендуется делать именно так.

Закончили с внешним видом.

  1. Достаточно коротко и разумно, рационально описаны правила именования всего и всея в ядре. Там много подпунктов. Наименования должны быть которкими и ясными. Считается хорошим тоном не смешивать большие и маленькие буквы. Hungarian notation is forbidden.

Потом перескакиваем на особенности языка программирования.

  1. Отдельным пунктом и отдеальную ненависть у Грега вызывают typedef'ы.
    Они используются ровно для двух вещей. Первая: перегрузка int'а (во что-то разумное с известным размером). Вторая: написание безумных никому непонятных типов, про которые даже не известно, ссылки они или нет.

  2. goto удалить мусор
  3. По части комментариев. Первое: Это должен быть очень небольшой текст, который доносит до народа всего две мысли: _что_ делает функция и _зачем_ (если это не очевидно). В комментарии не должно быть написано, _как_ работает ваша фукнция. Второе: Комментирование функций, структур и юнионов следует делать по заранее определённому шаблону, чтобы можно было специальным инструментом их оттуда вытащить. Не doxygen’ом, а более простым инструментом на базе grep’а. В те времена никакого doxygen’а ещё не было. А цели документирования исходников ядра гораздо более узкие, чем те, которые можно достичь с помощью doxygen'а.
  4. Что делать, если вы написали плохую, негодную с точки зрения функцию. Отформатируйте её скриптом и если вас устроит результат, оставьте как есть.
  1. Пункт относится уже к специфике разработки ядра.
  2. Отдельный пункт говорит о том, какой обвязкой сопровождать структуры данных. Любая глобальная структура данных должна сопровождаться реф-каунтером.

Дальше пошла всякая специфика.

  1. Как оформлять макросы. Ну например, там сказано, что не надо пихать в макросы кусочки, которые меняют ход выполнения программы.
  2. Если вы хотите обеспечивать какой-то вывод событий ядра, то не надо самому это делать. Есть kernel messages.
  3. Отдельный разговор про то, как работать с памятью.
  4. Отдельный пункт Грег посвятил использованию/не использованию inline функций. Здесь ситуация не такая однозначная как с typedef'ами. У вас должны быть резоны на использование inline функций.

Почему inline может быть взломан?

Я один пункт проворонил:

  1. Спецификация на собственно функции. Смотрите какая штука. Язык программирования Си имеет свои особенности. 0 — это false, а 1 — это true. Если же идёт речь о выполнении функции, то всё наоборот. Предикаты возвращают ноль и не ноль, а действия возвращают success или errno.
  2. Следующий пункт называется: пожалуйста, используйте kernel macros.
  3. Ну ещё парочка примеров. Не вставлять директивы текстовых редакторов в код (например настройки ви).

Не используйте magic numbers. «В порт вывести число 245». Что оно означает? Чёрт его знает. Если вы используете какую-то константу, её имя должно быть дискриптивным. Для этого есть дефайны.

Заключение

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

Почему я начал с того, что не дочитал почти ни один другой coding style guide до конца? Потому что там люди пытаются стандартизировать гораздо больше. Показателен в этом смысле BSD coding style. Люди явно вознамерились написать, как правильно писать на Си. Или Google Style Guide — как писать по-гугловски на многих языках программирования. Подозреваю, что эффективность таких вот ненормально детализованных дисциплин зависит не от содержания этих дисциплин, а от мотивированности разработчиков на их соблюдение.

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

Напоследок анекдот: В 2011 году компания Microsoft попала в первую десятку (была седьмой) по количествую коммитов в ядро Линукс. HyperV надо было поддерживать. Код, который реализует его поддержку в ядре Линукс существует не первый и не второй год. Но то ли люди которые его писали были не очень грамотные, то ли ещё что, но в ядро его не принимали, потому что стайл гайд никак не соблюдались. И так несколько лет. После того, как они удосужились ознакомиться с Linux Kernel coding style или кто-то им подсказал, и они привели свой код в соответствие с ним, весь этот код, что они накодили за несколько лет, в 2011 году попал в ядро. Чтобы закрыть тему — крупные корпорации действительно являются крупными коммитерами в ядро, просто потому, что им надо поддерживать их железяки.

LecturesCMC/LinuxApplicationDevelopment2012/Conspects/05 (последним исправлял пользователь Nyarcel 2012-11-23 15:30:51)