Вступление
Доброго времени суток, Хабражитель. Сразу хочу оговорится, что название не означает, что я буду призывать не писать комментарии никогда, любая крайность в этом мире скорее всего ущербна. Я лишь хочу сказать, что желание написать комментарий в каком-либо месте почти всегда свидетельствует о более важной проблеме в коде, разобравшись с которой необходимость в комментировании пропадет.
Перед началом еще хочу сказать, что примеры буду приводить с использованием 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/
Добавить комментарий