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


Давайте разберём несколько важных принципов создания самодокументируемого кода.



Не используйте «магические числа»


Скажите, что означает эта строчка?


if (students.length > 23) {

Проверяет, больше ли студентов, чем 23? И что это означает? Почему именно 23, а не, скажем, 24?


«Магическое число» — число без контекста. Вам нужно будет потратить время и силы, чтобы этот контекст понять. Избавьтесь от лишней работы, сразу явно дайте числу обозначение:


const maxClassSize = 23;
if (students.length > maxClassSize) {

Попробуйте прочитать код теперь. Мы проверяем не «больше ли студентов, чем 23», а «больше ли студентов, чем вмещает класс».


Используйте ясные имена для переменных


Не знаю почему, но раньше я постоянно боялся делать имена переменных длинными. Что было глупо с моей стороны, так как rStuNms и fStuNms ужасны в сравнении с rawStudentNames и filteredStudentNames.


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


const fStuNms = stus.map(s => s.n) 
// в сравнении с
const filteredStudentNames = students.map(student => {
  return student.name;
});

Еще один полезный совет — используйте конвенции (соглашения об именах). Если переменная булева, начинайте её имя с is или has (isEnrolled: true). Если в переменной массив, используйте множественное число (students). Многие числа должны начинаться с min или max. А имена функций должны содержать глагол, например, createSchedule или updateNickname. Кстати, о функциях...


Пишите крошечными именованными функциями


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


Посмотрите пару секунд на этот код и скажите, что он делает:


const handleSubmit = (event) => {
  event.preventDefault();
  NoteAdapter.update(currentNote)
    .then(() => {
      setCurrentAlert('Saved!')
      setIsAlertVisible(true);
      setTimeout(() => setIsAlertVisible(false), 2000);
     })
     .then(() => {
       if (hasTitleChanged) {
         context.setRefreshTitles(true); 
         setHasTitleChanged(false);
       }
     });
   };

А теперь сделайте то же самое для кода:


const showSaveAlertFor = (milliseconds) => () => { 
  setCurrentAlert('Saved!')
  setIsAlertVisible(true);
  setTimeout(
    () => setIsAlertVisible(false), 
    milliseconds,
  );
};

const updateTitleIfNew = () => {
  if (hasTitleChanged) {
    context.setRefreshTitles(true); 
    setHasTitleChanged(false);
  }
};

const handleSubmit = (event) => {
  event.preventDefault();
  NoteAdapter.update(currentNote)
    .then(showSaveAlertFor(2000))
    .then(updateTitleIfNew);
};

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


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


Добавьте полезные описания тестов


Наверное, реже всего говорят о самодокументируемых тестах, а зря. 


Допустим, у нас есть такая функция:


const getDailySchedule = (student, dayOfWeek) => { 

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


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


describe('getDailySchedule тест', () => {
  it("получает расписание на месяц", () => { 
  it('если сегодня выходной, возвращает пустой массив', () => {  
  it('добавляет дополнительные занятия в конец дня', () => {

Это самый элегантный способ комментировать код без комментариев в коде.


Итог: читаемость важнее заумности


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

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


  1. le1ic
    19.12.2019 12:11

    Во-первых,

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

    Во-вторых
    const maxClassSize = 23;
    работает только в школьных примерах на 50 строчек. В большом проекте, увидев такую константу вы все равно не будете уверены. maxClassSize — это физическое ограничение помещения, или административное? К каким классам оно применяется? Энфорсится ли это ограничение на уровне модели в БД, или нужно быть готовым, что придет и большее число? Всегда ли это ограничение было таким. или в прошлом оно было больше или меньше? Что будет, если я поменяю эту константу? Она задает поведение, или она отражает какие-то внешние ограничения? И тд. и тп. Ответы на подобные вопросы и отличают реальное программирование от академического «сейчас я расскажу вам, как надо»


    1. germn Автор
      19.12.2019 12:17
      -1

      > Во-первых,

      Можете показать хоть один более-менее большой проект, где хороший код полностью заменил бы документацию?

      > Во-вторых

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


    1. GarryC
      19.12.2019 13:25

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


      1. le1ic
        19.12.2019 19:36

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


    1. dipsy
      19.12.2019 13:54
      +3

      Вроде у автора во первых строках повествования как раз это оговорено, если я правильно понял.

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

      Ну типа не воспринимайте всё так буквально, ищите баланс, в этом основное искусство ремесла, так сказать.
      if (x < 23) плохо и if (x < maxClassSizeInPeoplePhysicalCapacityShouldBeMoreThan20) тоже не очень.


      1. GarryC
        19.12.2019 14:00

        Чем то напоминает известный ответ т. Сталина на вопрос, какой из уклонов (левый или правый) хуже: «Оба хуже».


    1. JediPhilosopher
      19.12.2019 14:53

      > смысл как раз что бы заменить.
      Дурость полная.
      Самый лучший, идеально написанный и толковый код может неплохо пояснить ЧТО он делает, но никогда не объяснит ПОЧЕМУ. Тут без документации никак не обойтись.

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


      1. le1ic
        19.12.2019 19:53

        Документирование решений и ожидаемого поведения системы – это конечно хорошо. Но это не документация уровня кода. Это более высокий уровень. Она безусловно полезна. Точно полезнее документирования кода.

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

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


        1. JediPhilosopher
          19.12.2019 21:57

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


          1. le1ic
            20.12.2019 00:56

            Я думаю тут нет предмета для спора


  1. sbnur
    19.12.2019 17:32

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


    1. AlfredZh
      20.12.2019 01:12

      Да, стоило.
      «Банальные истины» — это переменная; функция опыта, так сказать.


  1. Alexey2005
    19.12.2019 23:14

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


    1. le1ic
      20.12.2019 00:58

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


      1. germn Автор
        20.12.2019 01:35

        > необходимость прокидывать гигантский контекст в десятки переменных между ними

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

        Но ситуации бывают разные, согласен. Мелкие функции — не догма, а инструмент. Прагматичность всегда важнее.


        1. le1ic
          20.12.2019 10:21

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

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

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


    1. germn Автор
      20.12.2019 01:29
      +1

      > У множества мелких функций есть другой недостаток: за ними сложнее видеть алгоритм работы.

      Понимаете, а множество мелких функций — это и есть алгоритм работы.

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

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

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

      А удобная IDE поможет быстро перейти к реализации интересующей мелкой функции.