Чтобы вы не оставляли вводящую в заблуждение, устаревшую или совершенно неверную информацию.
Комментарии могут быть проблематичными. Вы не можете доверять им на 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.