Не пишите комментарии к коду!

от автора

Вступление

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

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

Для понимания проблемы обратимся к Вики, а после перейдем к примерам:

Коммента́рии — пояснения к исходному тексту программы, находящиеся непосредственно внутри комментируемого кода.

Пример комментариев к коду

public static void main(String[] args) throws IOException {     // "ua.in.link.rest.server" - name of package with classes for Jersey server     ResourceConfig rc = new PackagesResourceConfig("ua.in.link.rest.server");     // creating httpServer for url "http://localhost:8080"     HttpServer httpServer = GrizzlyServerFactory.createHttpServer("http://localhost:8080", rc);         InetSocketAddress socketAddres = httpServer.getAddress();         doSomethisWithSocket(socketAddres);        // waiting for Enter before stoping the server     System.in.read();     httpServer.stop(); } 

Константы

Давайте поглядим на данный пример. Начнем с таких строк:

    // "ua.in.link.rest.server" - name of package with classes for Jersey server     ResourceConfig rc = new PackagesResourceConfig("ua.in.link.rest.server"); 

Для человека, который не работал с локальным сервером Jersey, может быть действительно неочевидно, что за строка передается в конструкторе («ua.in.link.rest.server»). И автор, наверняка, хотел сделать данную часть более понятной. Однако, теперь, если по каким-либо причинам имя пакета в конструкторе будет изменено существует вероятность того, что комментарий останется старый, а, как известно, устаревший комментарий хуже отсутствующего. Более того, как уже говорил ранее, желание вставить комментарий в код свидетельствует (почти всегда) об проблемах с кодом. В данном примере думаю понятно, что проблемой является «захардкоденная» строка пакета(«ua.in.link.rest.server»). И если ее вынести в отдельную сущность, мы будем вынуждены именовать ее, для связки ее с данным кодом. Например, мы вынесли ее как константу:

private static final String JERSEY_CLASSES_PACKAGE_NAME = "ua.in.link.rest.server";   public static void main(String[] args) throws IOException {     ResourceConfig rc = new PackagesResourceConfig(JERSEY_CLASSES_PACKAGE_NAME);     // creating httpServer for url "http://localhost:8080"     HttpServer httpServer = GrizzlyServerFactory.createHttpServer("http://localhost:8080", rc);         InetSocketAddress socketAddres = httpServer.getAddress();         doSomethisWithSocket(socketAddres);         // waiting for Enter before stoping the server     System.in.read();     httpServer.stop(); } 

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

Разделяй и властвуй

Идем далее… Обратим внимание на вот эти строки:

    // creating httpServer for url "http://localhost:8080"     HttpServer httpServer = GrizzlyServerFactory.createHttpServer("http://localhost:8080", rc);         InetSocketAddress socketAddres = httpServer.getAddress();         doSomethisWithSocket(socketAddres); 

И тут комментарий наносит непоправимую пользу=). Вроде и так понятно, что делаю строки, комментарий дописан, так как метод в данном примере решает более чем одну задачу и нужно разграничить код, очень часто это делают комментариями, как тут. Что мы в итоге имеем. Допустим, что сервер стартует в каком-либо другом месте и из данного места старт сервера необходимо убрать. Нам нужно точно знать какой код отвечает за его старт. И вот проблема. Отвечает за старт только эта строка:

    HttpServer httpServer = GrizzlyServerFactory.createHttpServer("http://localhost:8080", rc);     

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

    HttpServer httpServer = GrizzlyServerFactory.createHttpServer("http://localhost:8080", rc);         InetSocketAddress socketAddres = httpServer.getAddress();         doSomethisWithSocket(socketAddres); 

Непонятно, какие строки необходимо убрать в случаи, если сервер запускается где-то на стороне. Более того, при модификации кода очень легко оставить строку:

    ResourceConfig rc = new PackagesResourceConfig(JERSEY_CLASSES_PACKAGE_NAME); 

Не заметив что она так же относится к блоку создания сервера.

Давайте теперь отрефакторим данный код.

private static final String JERSEY_CLASSES_PACKAGE_NAME = "ua.in.link.rest.server";   private static final String SERVER_URL = "http://localhost:8080";   public static void main(String[] args) throws IOException {         HttpServer httpServer = startServer();              InetSocketAddress socketAddres = httpServer.getAddress();         prepareSocket(socketAddres);           // waiting for Enter before stoping the server     System.in.read();     httpServer.stop(); }   private static HttpServer startServer() {     ResourceConfig rc = new PackagesResourceConfig(JERSEY_CLASSES_PACKAGE_NAME);     return GrizzlyServerFactory.createHttpServer(SERVER_URL, rc);     }   private static void prepareSocket(InetSocketAddress socketAddres){     doSomethisWithSocket(socketAddres);        } 

Теперь очевидно, что операции с Socket не касаются факта запуска сервера, иначе они бы выполнялись в методе старта сервера, как-то так:

private static HttpServer startServer() {     ResourceConfig rc = new PackagesResourceConfig(JERSEY_CLASSES_PACKAGE_NAME);     HttpServer httpServer = GrizzlyServerFactory.createHttpServer(SERVER_URL, rc);         InetSocketAddress socketAddres = httpServer.getAddress();         prepareSocket(socketAddres);      return httpServer; } 

Теперь ясно, что именно отвечает за запуск сервера. Более того, если более точно представляешь какой код за что отвечает, то более просто локализовтаь баг.

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

private static final String JERSEY_CLASSES_PACKAGE_NAME = "ua.in.link.rest.server";   private static final String SERVER_URL = "http://localhost:8080";  private static final String PRESS_ENTER__TO_STOP_STRING = "Press Enter to stop server";   public static void main(String[] args) throws IOException {         HttpServer httpServer = startServer();              InetSocketAddress socketAddres = httpServer.getAddress();         prepareSocket(socketAddres);           userStopsServer(httpServer); }   private static HttpServer startServer() {     ResourceConfig rc = new PackagesResourceConfig(JERSEY_CLASSES_PACKAGE_NAME);     return GrizzlyServerFactory.createHttpServer(SERVER_URL, rc);     }   private static void prepareSocket(InetSocketAddress socketAddres){     doSomethisWithSocket(socketAddres);        }   private static void userStopsServer(HttpServer httpServer){     System.out.println(PRESS_ENTER__TO_STOP_STRING + httpServer.toString());     System.in.read();     httpServer.stop(); } 

А где нужны комментарии?

Очень удачно по поводу тестов сказано в Вики, которую я и процитирую:

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

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

И все же, иногда нету иного пути, кроме как прибегнуть к неочевидному решению. Пример этому — использование более старой версии библиотеки или @Depricated методов. Но все эти случаи на практике встречаются куда реже, чем кажется.

JavaDoc

Множество проектов, уже на пред-релизном этапе, изобилуют стандартными комментариями вида:

/** * Имя или краткое описание объекта *  * Развернутое описание *  * @имя_дескриптора значение * @return тип_данных */ 

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

Вместо выводов

Само собой комментарии — это весьма холиварная тема. Много, кто может полезть в открытые проекты, которыми я заведую и, указав на какой -то код, сказать — «без комментариев он не очевиден» и будет абсолютно прав. Однако, если у меня будет время заняться этой частью кода, я потрачу это время на рефакторинг а не на написание комментариев, и лишь будучи загнан в угол и не придумав как применить принцип KISS, покорно напишу комментарий, но не ранее. Так же хочется сказать, что о комментариях часто напоминают молодые программисты, которым на самом деле банально не хватает практики, что бы увидеть в том или ином коде всем известный паттерн, подход или незнание работы каких-либо библиотек. Но это не проблема (и точно не необходимость) комментариев, а просто отсутствие должного опыта.

ссылка на оригинал статьи http://habrahabr.ru/post/178653/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *