Чтобы вы не оставляли вводящую в заблуждение, устаревшую или совершенно неверную информацию.

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

1. Правильно называйте вещи

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

class MessageBuffer 
{
public:
  // Return false if buffer full,
  // true if message written to buffer
  bool write(const std::shared_ptr<Message>& message);
}

Если мы отбросим комментарий, у нас будет следующий код:

class MessageBuffer 
{
public:
  bool write(const std::shared_ptr<Message>& message);
}

Отбрасывание комментария в одиночку — не лучшее решение, потому что сейчас отсутствует важная информация. Что означает это логическое возвращаемое значение? Это не на 100% ясно. Мы можем предположить, что возврат true означает, что сообщение было успешно записано, но о возврате false ничего не сообщается. Мы можем только предположить, что это какая-то ошибка, но не уверены, какая именно.

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

class MessageBuffer 
{
public:
  bool writeIfBufferNotFull(const std::shared_ptr<Message>& message);
}

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

Ниже приведен реальный пример из книги, которую я когда-то читал:

public interface Mediator {
  // To register an employee
  void register(Person person);
  
  // To send a message from one employee to another employee
  void connectEmployees(Person fromPerson,
                        Person toPerson,
                        String msg);
  
  // To display currently registered members
  void displayDetail();
}

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

Вторая функция говорит в комментарии, что отправляет сообщение от одного сотрудника другому. Имя функции говорит о подключении сотрудников, а параметры — это лица. Я предполагаю, что часть комментария верна: отправить сообщение от кого-то другому. Но опять же, я больше доверяю коду, чем комментарию, и предполагаю, что сообщение отправляется от одного человека к другому. Мы должны удалить комментарий и переименовать функцию.

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

Ниже приведена рефакторинговая версия без комментариев:

public interface Mediator {
  void register(Person person);
  
  void send(String message, 
            Person sender,
            Person recipient);
  
  void displayDetailsOfRegisteredPersons();
}

2. Извлечение константы для логического выражения

Извлекая константу для логического выражения, мы можем исключить комментарии. Ниже приведен пример, когда комментарий написан под оператором if и его логическим выражением:

bool MessageBuffer::writeIfBufferNotFull(
  const std::shared_ptr<Message>& message
) {
  bool messageWasWritten{false};
  
  if (m_messages.size() < m_maxBufferSize)
  {
    // Buffer is not full
    m_messages.push_back(message);
    messageWasWritten = true;
  }
  
  return messageWasWritten;
}

Введя константу для проверки «буфер заполнен», мы можем избавиться от комментария «Буфер не заполнен»:

bool MessageBuffer::writeIfBufferNotFull(
  const std::shared_ptr<Message> message
) {
  bool messageWasWritten{false};
  
  const bool bufferIsNotFull =
    m_messages.size() < m_maxBufferSize;
  
  if (bufferIsNotFull)
  {
    m_messages.push_back(message);
    messageWasWritten = true;
  }
  
  return messageWasWritten;
}

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

Если вы столкнулись с магическим числом в своем коде, вы должны ввести для этого значения либо именованную константу, либо перечисляемый тип (enum). В приведенном ниже примере мы возвращаем два магических числа, 0 и 1:

int main()
{
  Application application;
  
  if (application.run())
  {
    // Application was run successfully
    return 0;
  }

  // Exit code: failure
  return 1;
}

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

enum class ExitCode
{
  Success = 0,
  Failure = 1
};

int main()
{
  ExitCode exitCode;
  Application application;
  const bool appWasSuccessfullyRun = application.run();
  
  if (appWasSuccessfullyRun)
  {
    exitCode = ExitCode::Success;
  }
  else
  {
    exitCode = ExitCode::Failure;
  }

  return static_cast<int>(exitCode);
}

Теперь при необходимости легко добавить дополнительные коды выхода с описательными именами.

4. Извлеките функцию

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

void MessageBuffer::writeFitting(
  std::deque<std::shared_ptr<Message>>& messages
) {
  if (m_messages.size() + messages.size() <= m_maxBufferSize)
  {
    // All messages fit in buffer
    m_messages.insert(m_messages.end(),
                      messages.begin(),
                      messages.end());
                      
    messages.clear();
  }
  else
  {
    // All messages do not fit, write only messages that fit
    const auto messagesEnd = messages.begin() +
                             m_maxBufferSize -
                             m_messages.size();
                             
    m_messages.insert(m_messages.end(),
                      messages.begin(),
                      messagesEnd);
                      
    messages.erase(messages.begin(), messagesEnd);
  }
}

Вот тот же код с рефакторингом комментариев путем извлечения двух новых методов:

void MessageBuffer::writeFitting(
  std::deque<std::shared_ptr<Message>>& messages
) {
  const bool allMessagesFit = m_messages.size() +
                              messages.size() <= m_maxBufferSize;
                              
  if (allMessagesFit)
  {
    writeAll(messages)
  }
  else
  {
    writeOnlyFitting(messages);
  }
}

void MessageBuffer::writeAll(
  std::deque<std::shared_ptr<Message>>& messages
) {
  m_messages.insert(m_messages.end(),
                    messages.begin(),
                    messages.end());
                    
  messages.clear();
}

void MessageBuffer::writeOnlyFitting(
  std::deque<std::shared_ptr<Message>>& messages
) {
  const auto messageCountThatFit = m_maxBufferSize -
                                   m_messages.size();
                                   
  const auto messagesEnd = messages.begin() +
                           messageCountThatFit;
                           
  m_messages.insert(m_messages.end(),
                    messages.begin(),
                    messagesEnd);
                    
  messages.erase(messages.begin(), messagesEnd);
}

5. Назовите анонимную функцию

Анонимные функции распространены в функциональном программировании, например, при использовании таких алгоритмов, как forEach, map, filter и reduce. Если анонимная функция длинная или сложная, дайте ей описательное имя и разделите ее на несколько функций, если она слишком длинная. Таким образом, вы можете удалить комментарии.

В приведенном ниже примере TypeScript у нас есть анонимная функция с комментарием:

fs.watchFile('/etc/config/LOG_LEVEL', () => {
  // Update new log level
  try {
    const newLogLevel = fs.readFileSync('/etc/config/LOG_LEVEL',
                                        'utf-8'}).trim();
    tryValidateLogLevel(newLogLevel);
    process.env.LOG_LEVEL = newLogLevel;
  } catch (error) {
    // ...
  }
});

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

function updateNewLogLevel() {
  try {
    const newLogLevel = fs.readFileSync('/etc/config/LOG_LEVEL',
                                        'utf-8'}).trim();
    tryValidateLogLevel(newLogLevel);
    process.env.LOG_LEVEL = newLogLevel;
  } catch (error) {
    // ...
  }
}

fs.watchFile('/etc/config/LOG_LEVEL', updateNewLogLevel);
Want to Connect?

For more content on clean code principles, practices, and design patterns, 
connect with me on Twitter or Medium.

Вы можете найти приведенные выше и другие советы в моей книге 📕 Принципы и шаблоны чистого кода: руководство для программистов, которая доступна на Amazon.