No comments

Автор оригинала: S. G.
  • Перевод


«Комментарии должны составлять 5% от общего количества баллов», — заявил мой коллега-преподаватель.

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

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

Я с жаром ему возразил. Приучение к обязательной вставке комментариев — это, наверно, самая вредная привычка программистов, которой обучают в вузах. Если не считать некоторых случаев, в которых использование комментариев оправдано (подробнее о них далее), добавление комментариев — это антипаттерн, а чрезмерно закомментированные кодовые базы, скорее всего, нуждаются в рефакторинге.

Подождите, что за ересь? Комментарии никому не мешают. Или всё-таки мешают?

Есть две причины, по которым я считаю комментарии антипаттерном:

  1. Они неизбежно устаревают. Разработчики забывают обновлять комментарии при рефакторинге кода и создании новых функций. Когда такое происходит, комментарии становятся первопричиной того, чему должны были препятствовать, а именно запутанности. Особенно справедливо это для крупных компаний, в которых множество людей активно изменяет общую кодовую базу. Представьте состояние новичка, который не может разобраться, почему дом имеет комментарий, называющий его «пальмой» (Palm Tree).
  2. Программисты (в том числе и я) пишут избыточные или плохие комментарии. Я замечал это в каждой из компаний, где я работал, и этим грешат многие превосходные программисты. В моей команде в Google было правило, что каждому определению буфера протокола должен предшествовать комментарий. Поэтому у нас была куча примеров кода, который выглядел примерно так:

// Represents a Dog.
message Dog {
    // The id of the dog.
    string id = 0;
    // The name of the dog.
    string name = 1;
    // The breed of the dog.
    string breed = 2;
}

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

Ну ладно, ваша точка зрения понятна. Избыточные комментарии вредны. Но как насчёт случаев, когда комментарии необходимы для объяснения того, что делает код?

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

В этом примере мы отправляем оплату продавцам при выполнении определённых условий. Например, это может быть продавец в Amazon, получающий общую сумму платежей за свои продажи на платформе:

# Seller is eligible to be paid under the following conditions:
# 1. It's past 2020/5/1 when we got legal approval to make payouts
# 2. It’s Nov/Dec since those are the only months eligible
# 3. User is not from list of countries that are banned
today = date.today()
if today > date(2020, 1, 1) and (today.month == 11 or today.month == 12) and user.country not in ['Narnia', 'Odan', 'Maldonia']:
    # This function does the actual payout by calling Stripe
    # It saves the response asynchronously.
    payoutHandler()

Поначалу кажется, что использование комментариев оправдано, ведь они повышают читаемость кода. Код читают чаще, чем пишут, поэтому улучшение читаемости — это хорошо. В данном примере комментарии передают следующую информацию:

  1. Условия выплаты. Без комментария в начале читателю самому бы пришлось разбираться в парсинге кратких подусловий в условном операторе, чтобы понять, должен ли выполняться платёж.
  2. Почему использованы конкретные константы (2020/5/1, 11, 12 и [‘Narnia’, ‘Odan’, ‘Maldonia’]) и что они обозначают.
  3. Что делает метод payoutHandler().

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

PAYOUT_APPROVAL_DATE = date(2020, 5, 1)
BANNED_COUNTRIES = ['Narnia', 'Odan', 'Maldonia']
NOVEMBER, DECEMBER = 11, 12
ELIGIBLE_MONTHS = [NOVEMBER, DECEMBER]
today = date.today()
is_past_approval_date = today > PAYOUT_APPROVAL_DATE
is_eligible_month = today.month in ELIGIBLE_MONTHS
is_user_from_banned_country = user.country in BANNED_COUNTRIES
if is_past_approval_date and is_eligible_month and not is_user_from_banned_country:
 stripe_payout_resp = callStripeToPayout(user)
 saveResponseAsync(stripe_payout_resp)

Обратите внимание, что информация, которая раньше передавалась в комментариях, теперь передаётся при помощи временных переменных (is_past_approval_date, is_eligible_month, is_user_from_banned_country), констант (BANNED_COUNTRIES) и глаголов в качестве имён функций.

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

То есть комментарии никогда не нужно использовать? Какое-то слишком строгое требование.

Я пользуюсь таким эмпирическим правилом: не использовать комментарии, отвечающие на вопрос «что?». Используйте их, чтобы объяснить «зачем?», и только в тех случаях, когда «зачем?» нельзя объяснить именами. В большинстве случаев для объяснения «зачем?» достаточно правильно подобранных имён. Например, вместо добавления такого комментария:

# This code removes the invalid control characters because the 
# AS400 processor cannot handle them

назовите свою функцию

def remove_AS400_incompatible_control_chars():

Если при передаче информации нельзя положиться на имена, например, если код делает что-то мелкое или неочевидное, то комментарии, разумеется, обоснованы. Вот некоторые примеры полезных комментариев:

# Prevents a rare but severe race condition which happens when...

# Closing this write transaction before making the RPC to prevent stale locks

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



Маклауд предлагает высокопроизводительные серверы для любых задач — до 128 vCPU, 512 ГБ RAM.
Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!

Маклауд
Облачные серверы на базе AMD EPYC

Комментарии 48

    +7
    В КДПВ можно спокойно заменить комментарий на class PalmTree не потеряв шутки.
      +14
      А теперь давайте посмотрим, как можно переписать этот код, чтобы он передавал ту же информацию без использования комментариев:

      Действительно! Стало гораздо понятнее, короче и читабельнее!

        +19

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


        PAYOUT_APPROVAL_DATE = date(2020, 5, 1)
        BANNED_COUNTRIES = ['Narnia', 'Odan', 'Maldonia']
        ELIGIBLE_MONTHS = [11, 12]
        today = date.today()
        
        if (today > PAYOUT_APPROVAL_DATE) 
            and (today.month in ELIGIBLE_MONTHS) 
            and (not user.country in BANNED_COUNTRIES):
        
         stripe_payout_resp = callStripeToPayout(user)
         saveResponseAsync(stripe_payout_resp)

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


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

          +6

          Мы сейчас перерабатываем с моей командой структуру доков и комментариев в кодовой базе.


          Один из пойнтов — комментарий или док должен давать более абстрактное описание, нежели код или сообщение при коммите, отвечая на вопросы:
          Зачем?/Почему именно так?, притом как минимум на уровне файла, а лучше — модуля.


          Если знакомы с научными статьями, аналогия такая — комментарий это abstract вашего кода.


          При это используется фоллбэк
          Код -> коммит message -> комментарий.
          Логика такая:
          Если я читаю код — комментарий задает мне абстрактный контекст, в котором я его интерпретирую, а если мне не понятна конкретная строка — я могу залезть в историю чтобы ее отследить.
          Обратно — если я меняю код, то комментарий должен меняться только при концептуальном изменении — рефакторинге, например. При минорных изменениях код поменяется в любом случае, а сообщение к коммиту ответит на вопрос «зачем меняли».


          Проблемы устаревания в таком случае не возникает- комментарий не дублирует код, а задает контекст, например доменный, или по performance.


          PS
          Если кому-нибудь будет интересно, могу потом написать статейку о результатах, годика через пол)

            +1
            > комментарий это abstract вашего кода.

            Ой, спасибо! Хорошая аналогия для комментариев «в шапке» файла/программы.

            Вообще всё очень сильно зависит от времени жизни кода, от того, как оно будет использоваться, сколько человек и т.д. Комментарии для прикладной программы с коротким временем жизни строятся одним образом — достаточно шапки, а для Stdlib — ну совершенно другое.
        +11
        Комментарии нужны что бы в художественной форме рассказать что делает каждый участок кода, описать нюансы. Что бы по ключевым словам можно было найти нужный участок кода более оперативно. Помочь ревьюверу или новому сотруднику проникнуться этой историей.

        Много воды плохо. Сухость — плохо. Не комментируйте сам код, комментируйте процесс.
          +2
          Комментарии излишне! (к статье тоже )
            +9

            Вот как выглядит код с хорошими комментариями:


                /// Creates the specified directory with the options configured in this
                /// builder.
                ///
                /// It is considered an error if the directory already exists unless
                /// recursive mode is enabled.
                ///
                /// # Examples
                ///
                /// ```no_run
                /// use std::fs::{self, DirBuilder};
                ///
                /// let path = "/tmp/foo/bar/baz";
                /// DirBuilder::new()
                ///     .recursive(true)
                ///     .create(path).unwrap();
                ///
                /// assert!(fs::metadata(path).unwrap().is_dir());
                /// ```
                #[stable(feature = "dir_builder", since = "1.6.0")]
                pub fn create<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
                    self._create(path.as_ref())
                }

            (и далее по тексту: https://doc.rust-lang.org/src/std/fs.rs.html#91-93)


            Hint: Это не совсем комментарии, это документация, превращающаяся в https://doc.rust-lang.org/std/fs/struct.File.html)


            Вот это — хорошие комментарии.

              0

              Это аннотация к методу, а у неё могут быть довольно странные способы применения, вплоть до генерации роутов где-нибудь в Symfony.

                0

                Я показываю как хорошие институционализированные (т.е. не объяснение "нафига тут 42", а для каждого поля и метода) комментарии должны выглядеть.
                Во-первых машиночитаемая структура (У Rust комментарий — 2 слеша, документация — 3).
                Во-вторых она процессится в человекочитаемую форму и создаёт документацию к коду.
                В третьих она может тестироваться.


                И вот такие комментарии реально можно требовать ко всему публичному в коде.

                +7

                Главный плюс этого комментария — пример в нем компилируется, а значит проверяется на достоверность. :)

                  0
                  1. Не факт что компилируется, если он не был протестирован via cargo test
                  2. Пример может вообще не иметь отношения к нижележащему коду или стать той самой пальмой, которую никто не обновлял — примеры и тесты также нуждаются в поддержке, как и основной код.
                    0
                    1. Ну, тут всё просто, настраивается CI/CD, который и вызывает cargo test.
                    2. Да, тут сложнее возможна ситуация с пальмой, но всё равно легче отловить такого рода ошибку и инструментарий подталкивает тебя делать правильно
                +12
                Самый лучший комментарий, что я видел был в ядре Винды: ASCII-иллюстрации балансировки дерева. И «Knuth says it is obvious!»
                github.com/HighSchoolSoftwareClub/Windows-Research-Kernel-WRK-/blob/26b524b2d0f18de703018e16ec5377889afcf4ab/WRK-v1.2/base/ntos/mm/addrsup.c#L606
                +1

                А если метод немного поменял внутреннюю логику, но не изменилась суть, или условие немного сдвинулось, а нейминг функций оставили.

                  +5
                  1. Разработчики забывают обновлять комментарии при рефакторинге кода и создании новых функций.
                  2. Программисты (в том числе и я) пишут избыточные или плохие комментарии.

                  И из этого делается вывод о ненужности комментариев. Браво!
                    +9
                    А теперь давайте посмотрим, как можно переписать этот код, чтобы он передавал ту же информацию без использования комментариев:

                    Пожалуй, я лучше продолжу писать комментарии...

                      0
                      Комментарии менять просто и дешево, но вот менять имена функций… ну такое себе.
                      Итерация 1:
                      # This code removes the invalid control characters because the AS400 processor cannot handle them
                      def remove_incompatible_control_chars():
                      # ... vs ...
                      def remove_AS400_incompatible_control_chars():

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

                      Итерация 2:
                      # This code removes the invalid control characters because the AS400 and AS500 processors cannot handle them
                      def remove_incompatible_control_chars():
                      # ... vs ...
                      def remove_AS400_AS500_incompatible_control_chars():



                        0
                        Переименование функции внутри кода проекта — это пару нажатий клавиш.
                        Но в данном случае создаётся функция remove_AS500__incompatible, возвращается функция remove_incompatible_control_chars в которую помещаются вызовы функций remove_AS400 и remove_AS500.
                        +6
                        Код описывает только то, что делает код. Зачем он это делает и какие были намерения из кода узнать невозможно.
                          0
                          Как бы эта самая мысль в самой статье и продвигается:

                          Я пользуюсь таким эмпирическим правилом: не использовать комментарии, отвечающие на вопрос «что?». Используйте их, чтобы объяснить «зачем?»


                          Потому что на вопрос «что делает код» лучше и надежнее всего всего отвечает сам код. А комментарии оставить как раз на описание «зачем» и каких-нибудь специфических/неочевидных (из самого кода) моментов.
                          +1
                          Избыточные комментарии — это шум, способный сделать файлы в два раза длиннее, чем они должны быть.
                          Тут согласен. Но дальше пошло не про избыточные комментарии, а про то, что их вообще всегда нужно избегать. С этим не согласен. Такой подход на деле обернётся вечными спорами между программистами в команде.
                            +4
                            И самое смешное, что в комментариях — ошибка

                            # 1. It's past 2020/5/1 when we got legal approval to make payouts

                            vs

                            if today > date(2020, 1, 1)
                              +1
                              Почему именно в комментариях? Ошибка может быть и в коде. А может, и ещё веселее — не ошибка, а фича. Когда по каким-то внутренним причинам today в данной программе смещено от реальной даты на несколько дней, из-за чего и приходится сверять с несколько другой константой.
                              +4
                              1. Комментарии устаревают
                              Вообще вся документация устаревает. Откажемся от документирования?
                              2. Программисты пишут плохие комментарии
                              … да и код тоже частенько так себе. Особенно полохими комментарии получаются когда разработчиков насильно заставляют их писать, как в приведённом примере про собачку.
                                0
                                В документации есть версионность, в комментах версионности нет. Понять что коммент больше не применим потому что функция которую он коментит изменилась невозможно.
                                  0

                                  Что за глупость говорите, есть там версионность, у кода же она есть, почему у комментариев, расположенных прямо рядом с кодом, её нет? Следить просто за этим нужно (как и везде).

                                    0
                                    А как понять, что документация не применима, потому что функция, которую она комментит изменилась?
                                    +1
                                    На всех legacy-проектах с которыми я сталкивался документация не соответствовала действительности.
                                    Более того. Само название классов, методов и полей могло означать всё что угодно.
                                    Так что я лично скептически отношусь, к тому что написано и что нельзя проверить.

                                    +1
                                    Код (с комментами или без) должен быть лаконичен. Это очевидно?
                                    В примере:

                                    is_user_from_banned_country


                                    можно урезать знаки «верблюжачим методом»:

                                    isUserFromBannedCountry

                                    Но если нужна функция, которая вернет 0, если ok; 1, если «user_from_banned_country»; 2, если " past_approval_date"; 3, если «eligible_month», то как ее назвать, чтобы идентификатор отображал всю функциональность? И комменты были бы не нужны.
                                    ИМХО уже пример статьи показал, что длинные имена способны раздуть код больше, чем одноразовый коммент. Сравним:

                                    // is user from banned country

                                    с повторами:

                                    is_user_from_banned_country

                                    is_user_from_banned_country

                                    А еще бывает, что пришедший на смену кодер плохо знает язык, тогда получим «красивые» имена:

                                    esli_ne_is_user_from_banned_country_ili_past_approval_date_to_vidat_oshibky_i_soxranit_v_loge
                                      +3
                                      Но если нужна функция, которая вернет 0, если ok; 1, если «user_from_banned_country»; 2, если " past_approval_date"; 3, если «eligible_month», то как ее назвать, чтобы идентификатор отображал всю функциональность?

                                      Псевдокод:


                                      enum RejectionType {
                                          UserBannedFromCountry = 1,
                                          PastApprovalDate = 2
                                          EligibleMonth = 3
                                      }
                                      
                                      RejectionType? check_rejected(...) {
                                          if (not rejected) return null;
                                          ...
                                      }

                                      По вкусу можно переместить Ok в enum (и не забыть его переименовать), либо же возвращать Option/Maybe, если язык это позволяет. Но суть в том, что сигнатура функции (тип возврата) содержит информацию о том, какие именно проверки делаются внутри. При добавлении новой надо будет обязательно обновить перечисление, а при удалении старого статический анализатор заругается, что вариант никогда не создаётся. Плюс если его таки удалить, то придется пересмотреть все места использования, иначе не скомпилируется.

                                      +3
                                      Иногда, если требуется написать что то очень витьеватое и запутанно, я сначала пишу комментарии.
                                      Типа — сделаем это… потом сделаем это… тут не забудем учесть ещё и то…
                                      А потом к комментариям пишу сам код.

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

                                      Это, конечно же, никак не отменяет применение осмысленных имен переменных и функций.

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

                                        0
                                        Супер-хинт. Берешь и выпиливаешь вредные комментарии во всем коде, которого касается твоя рука.
                                          +2
                                          Код с большим количеством избыточных больших названий сложней менять — начинаешь путаться в названиях. Много комментариев так же не всегда хорошо, загромождают код, забываешь их править.

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

                                          По мне так луше где-нибудь в золотой серединке.
                                            0

                                            Да что тут спорить, комментарии — это просто такие спейсеры в коде (потому в большинстве редакторов они и показываются блеклым цветом), чтобы докблоки отделяли один метод в классе от другого посильнее. А то если докблоки не использовать, то несколько методов сливаются в сплошную кашу (потому что одной пустой строкой их разделять плохо — ибо пустая строка и в теле метода используется, а двойную пустую строку убирают автоформаттеры кода, которые стали сейчас особенно хорошо работать). Вот и все. Не важно, что там написано, это просто разделитель, выглядящий по-умному (так что трюк с «ничего не писать там, просто пусто» не прокатит, нужно хоть lorem ipsum, но текст). Не сарказм.

                                              0

                                              В python напротив ide будет подсказывать что надо бы две строки поставить вместо одной. Так что у вас частный пример

                                              +1
                                              у нас уже мемом стало. Не далось выяснить дат появления каждой строчки, видимо последний раз было перемещено в 2012 году уже состояшейся пачкой. Это флеш, он как бы умер, но благодаря моим некроманским стараниям проект жив и здоров.
                                              image
                                                0
                                                Разработчики забывают обновлять комментарии при рефакторинге кода и создании новых функций. Когда такое происходит, комментарии становятся первопричиной того, чему должны были препятствовать, а именно запутанности.


                                                ну так в этом и проблема. Нужно за этим следить.
                                                  0
                                                  Обратите внимание, что информация, которая раньше передавалась в комментариях, теперь передаётся при помощи временных переменных (is_past_approval_date, is_eligible_month, is_user_from_banned_country),

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

                                                  на самом деле, надо воспринимать комментарии (там, где они нужны для понимания смысла) как часть работающего кода

                                                  изменился смысл — поправь комменты, делов-то
                                                    +1

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

                                                      0

                                                      Статья очень напомнила главу из книги "Чистый код". Основные мысли схожи.

                                                        +2
                                                        «Комментарии должны составлять 5% от общего количества баллов», — заявил мой коллега-преподаватель.

                                                        Ура, мудрый прохвессор наконец-то расскажет нам, как программы программировать!</sarcasm>


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

                                                          0

                                                          Щас появиться статья про то что тесты тоже зло. Кстати с тестами как и комментариями дело обстоит. Все подряд тестами покрывать точно не нужно, если за это конечно специально не платят. Цель тестов — корректно работающий код. Цель комментариев — улучшить понимание кода

                                                            0

                                                            Тесты — это другое. Есть случаи (и много), когда без тестов вообще невозможно что-то сделать даже теоретически. («Невозможно» = «в 100 раз сложнее», скажем; на практике это одно и то же.) Это многоцелевые библиотеки, биллинги и т.д.

                                                              0
                                                              Тесты на библиотеки ой как помогают понять как работает работать с кодом. И я иногда даже не знаю как обозначить код, как примеры, или как тесты (если речь опять о библиотеке).
                                                            0
                                                            В лекции Clean Code — Uncle Bob, повествует о том, что комментарии в коде были необходимы, когда длина имени переменной или функции была ограничена 6 символами в Фортране.

                                                            Я однажды попросил коллегу прокомментировать здоровенную портянку кода, и он прокомментировал всё тоже самое, что было видно из кода: «берём данные, собираем в объект и пуляем на сервер». Я, естественно, ожидал увидеть пояснение смысла портянки, в целом, и ограничений, присутствующих в ней, в частности.

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

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

                                                              Самое читаемое