Самодокументируемый код – это (как правило) чушь +21


Всем привет!

Предваряя сегодняшнюю переводную публикацию, сразу отметим, что этот текст задуман как follow-up недавнему дискуссионному материалу "Прекратите усердствовать с комментариями в коде". Нас настолько впечатлила развернувшаяся там дискуссия и 189 комментариев по состоянию на 19.07.2019, что мы решили дать здесь слово и другому автору с портала Medium (Кристоферу Лейну), который практически по всем принципиальным вопросам полемизирует с тезисами Брайана Норлендера, автора первой статьи. Отметим, что в оригинале данная статья вышла на месяц позже предыдущей (16 мая и 16 июня), но собрала практически вдвое меньше аплодисментов (706 против 1,5K на момент публикации перевода). Посмотрим, что будет на Хабре…



Снимок взят с сайта rawpixels.com от автора Pexels

Я внимательно прочел отличную статью Синди Чеунг о технической документации и о том, почему разработчики должны лучше объяснять собственный код – и должен сказать, что полностью с ней согласен.

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

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

Знаете, это чушь… как правило.

Почему «самодокументирующийся код» — это чушь?


Допустим, вы пишете код так же круто, как Хемингуэй писал прозу. Возможно, ваш код супер-пупер чистый и понятный (другому разработчику). В конце концов, этот код написан технарем для технаря, и, независимо от того, каким чистым и лаконичным может казаться ваш код, он все равно не предназначен для чтения не-программистами, которые могли бы проворчать: «что, черт возьми, все это значит?!»

Почему же я считаю, что самодокументирующийся код – это полная ерунда? Позвольте изложить в деталях.

Причина 1: В программировании полно всяких приемчиков, которые не самодокументируются

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

Но код пишут ДЛЯ машин. Они куда лучше нас разбираются, что с ним делать, и именно для того, чтобы описать им это, у нас есть языки программирования. С людьми же нужно общаться на более человеческом языке, чтобы человек смог понять, что делает ваш софт.

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

Рассмотрим следующий простой блоб на C#. Я считываю файл, получаю его содержимое, а затем получаю кодировку файла при помощи StreamReader.

var fileContents = “”;
Encoding fileEncoding; using (var reader = new StreamReader(filePath, Encoding.Default,  true))
 {
   reader.Peek(); 
   fileEncoding = reader.CurrentEncoding;
   fileContents = reader.ReadToEnd();
 }

Если абстрагироваться от возможных неясностей со StreamReader, в остальном этот код достаточно прост, верно? Тогда… помилуйте, а что делается в этой строке?

reader.Peek();

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

reader.Peek(); //Вот так нужно заглянуть в файл, чтобы получить его кодировку.

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

Причина 2: Сложность по сути своей не самодокументируется

Если вам доводилось писать файлы BASH или BAT, то вы знаете, что действия, изложенные в таком файле, выполняются последовательно. Одна задача за другой. Файл напоминает коротенькую историю, которая читается от первой до последней строчки.

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

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

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

Причина 3: Синтаксис языков программирования в принципе не назовешь удобочитаемым

Просто взгляните на эту функцию jquery, вызывающую конечную точку API.

var myURL="https://some.url/path?access_token=my_token";

$.ajax({
    url: myURL+"&callback=?",
    data: "message="+someOtherData,
    type: 'POST',
    success: function (resp) {
        alert(resp);
    },
    error: function(e) {
        alert('Error: '+e);
    }  
});

Уф…

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

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

Для всех остальных язык программирования непонятен.

Что же делать?

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

Этап 1: Попробуйте написать документацию


Кощунственно звучит, верно? Написать документацию?! Ничего смешнее вы не могли придумать!

Серьезно, никто и не требует от вас написать «Войну и мир». Но постарайтесь описать в технической документации основные действия, валидацию и обработку ошибок – простым последовательным стилем.

  • Клиент вызывает конечную точку API /someurl/object/{id}
  • Контроллер API использует {id} (типа int), чтобы найти искомый объект в базе данных.
  • Если объект возвращает null, то контроллер API выдает клиенту HTTP-отклик 404 (файл не найден). API-контроллер логирует это в качестве предупреждения.
  • Если возвращенный объект — NOT null, то контроллер API преобразует этот объект в формат JSON и возвращает его вызывающей стороне с HTTP-откликом 200 (OK).

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

Этап 2: Нарисуйте схемы


Если написать простую документацию для вас все-таки затруднительно, то, как минимум, попробуйте начертить самые необходимые схемы, так как они зачастую служат тем самым «клеем», который помогает человеку со стороны соотнести ваш код с тем, что в нем происходит.
Посмотрите сайт websequencediagrams.com, где в простом текстовом формат можно описывать отличные диаграммы последовательностей – и затем их создавать.

Текст

title Service one to service two
Service two requester -> Service two http client: Get Contract
Service two http client -> Service one REST API: GET /api/contracts/{123}
Service one REST API -> Service one app logic: get Contract with id 123
Service one app logic -> Service one REST API: Contract
Service one REST API -> Service one REST API: Serialise to JSON / XML / etc.
Service one REST API -> Service two http client: Serialised data
Service two http client -> Service two http client : Deserialise to Contract
Service two http client -> Service two requester: Contract

Диаграмма, которая из него получается



Красиво!

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

Этап 3: Называйте ваши классы и действия на Едином Языке (Ubiquitous Language)


Как известно, Единый Язык – это концепция из DDD (предметно-ориентированного проектирования), где команде и пользователям требуется выработать язык, который описывал бы все классы и их взаимодействия. Такой язык понятен неспециалисту, поэтому клиенты, тестировщики, инструкторы и представители бизнеса смогут на нем прочитать и понять, что именно делает наша программа и каким образом решает проблемы пользователя в данной предметной области.

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

/// <summary>
/// Клиент сам отвечает за собственный вход в систему/выход из нее, а также за извлечение информации из своего профиля и управление ею 
/// settings
/// </summary>
public interface ICustomer
{
     Task<AuthenticationResult> Login(string username, EncryptedString password);
     Task Logout();
     Task<CustomerProfileInformation> GetMyProfile();
     Task SaveMyProfile(CustomerProfileInformation);
     Task<CustomerSettings> GetMySettings();
     Task SaveMySettings(CustomerSettings);

Хотя, это просто фрагмент кода, над ним простым и общепонятным языком написано, что здесь происходит.

Этап 4: Просто напишите комментарии


Если все вышеперечисленное кажется вам слишком, слишком обременительным – просто снабдите ваш код информативными комментариями. Возможно, прямо сейчас они вам не понадобятся (вы-то сейчас с головой погружены в код, вам и так все ясно), но в будущем они могут вам весьма пригодиться.

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

Надеюсь, эти советы будут вам полезны




К сожалению, не доступен сервер mySQL