Создание, анализ ирефакторинг
Скачать 3.16 Mb.
|
листинг 4 .3 . /** * * @param title Название диска * @param author Автор диска * @param tracks Количество дорожек на диске * @param durationInMinutes Продолжительность воспроизведения в минутах */ public void addCD(String title, String author, int tracks, int durationInMinutes) { CD cd = new CD(); cd.title = title; cd.author = author; cd.tracks = tracks; cd.duration = duration; cdList.add(cd); } журнальные комментарии Некоторые программисты добавляют комментарий в начало модуля при каждом его редактировании . Такие комментарии накапливаются, образуя своего рода журнал всех вносимых изменений . Я видел модули, в которых эти журнальные записи растягивались на десятки страниц . * Изменения (начиная с 11 октября 2001) * -------------------------- * 11.10.2001 : Реорганизация класса и его перемещение в новый пакет * com.jrefinery.date (DG); * 05.11.2001 : Добавление метода getDescription(), устранение класса * NotableDate (DG); * 12.11.2001 : С устранением класса NotableDate IBD требует включения * метода setDescription() (DG); исправление ошибок * в методах getPreviousDayOfWeek(), getFollowingDayOfWeek() * и getNearestDayOfWeek() (DG); * 05.12.2001 : Исправление ошибки в классе SpreadsheetDate (DG); 90 Плохие комментарии 91 * 29.05.2002 : Перемещение констант месяцев в отдельный интерфейс * (MonthConstants) (DG); * 27.08.2002 : Исправление ошибки в методе addMonths() с подачи N???levka Petr (DG); * 03.10.2002 : Исправление ошибок по сообщениям Checkstyle (DG); * 13.03.2003 : Реализация Serializable (DG); * 29.05.2003 : Исправление ошибки в методе addMonths (DG); * 04.09.2003 : Реализация Comparable. Обновление isInRange javadocs (DG); * 05.01.2005 : Исправление ошибки в методе addYears() (1096282) (DG); Когда-то создание и сопровождение журнальных записей в начале каждого мо- дуля было оправдано . У нас еще не было систем управления исходным кодом, которые делали это за нас . В наши дни длинные журналы только загромождают и усложняют код . Их следует полностью удалить из ваших программ . шум Также в программах нередко встречаются комментарии, не содержащие ничего, кроме «шума» . Они лишь утверждают очевидное, не предоставляя никакой новой информации . /** * Конструктор по умолчанию. */ protected AnnualDateRule() { } Да неужели? А как насчет этого: /** День месяца. */ private int dayOfMonth; И наконец, апофеоз избыточности: /** * Возвращает день месяца. * * @return день месяца. */ public int getDayOfMonth() { return dayOfMonth; } Эти комментарии настолько бесполезны, что мы учимся не обращать на них вни- мания . В процессе чтения кода наш взгляд просто скользит мимо них . Рано или поздно код вокруг таких комментариев изменяется, и они начинают лгать . Первый комментарий в листинге 4 .4 кажется уместным . Он объясняет, почему блок catch игнорируется . Но второй комментарий не несет полезной информации . Видимо, программист настолько вышел из себя при написании этих блоков try / catch в этой функции, что ему понадобилось «выпустить пар» . 91 92 Глава 4 . Комментарии листинг 4 .4 . startSending private void startSending() { try { doSending(); } catch(SocketException e) { // Нормально. Кто-то прервал запрос. } catch(Exception e) { try { response.add(ErrorResponder.makeExceptionString(e)); response.closeAll(); } catch(Exception e1) { // Ну хватит уже! } } } Вместо того чтобы давать выход чувствам в бесполезном комментарии, про- граммисту следовало понять, что раздражение можно было снять улучшением структуры кода . Ему стоило направить свою энергию на выделение последнего блока try / catch в отдельную функцию, как показано в листинге 4 .5 . листинг 4 .5 . startSending (переработанная версия) private void startSending() { try { doSending(); } catch(SocketException e) { // Нормально. Кто-то прервал запрос. } catch(Exception e) { addExceptionAndCloseResponse(e); } } private void addExceptionAndCloseResponse(Exception e) { try 92 Плохие комментарии 93 { response.add(ErrorResponder.makeExceptionString(e)); response.closeAll(); } catch(Exception e1) { } } Искушение создать очередной «шумовой комментарий» следует заменить ре- шимостью очистить код . Вы сами увидите, что это сделает вашу работу более приятной и эффективной . Опасный шум Комментарии Javadoc тоже бывают «шумовыми» . Какую пользу приносят следу- ющие комментарии (из хорошо известной библиотеки, распространяемой с от- крытым кодом)? Ответ: никакой . Это избыточные шумовые комментарии, вы- званные неуместным желанием как-то документировать свои действия . /** Имя. */ private String name; /** Версия. */ private String version; /** Название лицензии. */ private String licenceName; /** Версия. */ private String info; Прочитайте эти комментарии повнимательнее . Заметили ошибку копирования/ вставки? Если авторы не следят за ними в момент написания (или вставки), то как можно ожидать, что эти комментарии принесут пользу читателю? не используйте комментарии там, где можно использовать функцию или переменную Возьмем следующий фрагмент кода: // Зависит ли модуль из глобального списка // частью которой является наш код? if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem())) Его можно было бы перефразировать без комментария в следующем виде: ArrayList moduleDependees = smodule.getDependSubsystems(); String ourSubSystem = subSysMod.getSubSystem(); if (moduleDependees.contains(ourSubSystem)) Возможно (хотя и маловероятно), автор исходного кода сначала написал ком- ментарий, а затем — соответствующий ему код . Но после этого автор должен был переработать свой код, как это сделал я, чтобы комментарий можно было удалить . 93 94 Глава 4 . Комментарии Позиционные маркеры Некоторые программисты любят отмечать определенные позиции в исходных файлах . Например, недавно я обнаружил в одной из просматриваемых программ следующую строку: // Действия ////////////////////////////////// В отдельных случаях объединение функций под такими заголовками имеет смысл . Но в общем случае они составляют балласт, от которого следует изба- виться — особенно от назойливой серии косых черт в конце . Взгляните на дело под таким углом: заголовки привлекают внимание только в том случае, если они встречаются не слишком часто . Используйте их умеренно и только тогда, когда они приносят ощутимую пользу . При слишком частом упо- треблении заголовков читатель воспринимает их как фоновый шум и перестает обращать на них внимание . Комментарии за закрывающей фигурной скобкой Иногда программисты размещают специальные комментарии за закрывающи- ми фигурными скобками, как в листинге 4 .6 . Применение таких комментариев оправдано в длинных функциях с многоуровневой вложенностью, но они только загромождают компактные специализированные функции, которым мы отдает предпочтение . Итак, если у вас возникает желание прокомментировать закры- вающие фигурные скобки, лучше постарайтесь укоротить свои функции . листинг 4 .6 . wc.java public class wc { public static void main(String[] args) { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String line; int lineCount = 0; int charCount = 0; int wordCount = 0; try { while ((line = in.readLine()) != null) { lineCount++; charCount += line.length(); String words[] = line.split("\\W"); wordCount += words.length; } // while System.out.println("wordCount = " + wordCount); System.out.println("lineCount = " + lineCount); System.out.println("charCount = " + charCount); } // try catch (IOException e) { System.err.println("Error:" + e.getMessage()); } //catch } // main } 94 Плохие комментарии 95 Ссылки на авторов /* Добавлено Риком */ Системы контроля исходного кода отлично запоминают, кто и когда внес то или иное исправление . Нет необходимости загрязнять код подобными ссылками . Может показаться, что такие комментарии помогают другим определить, с кем следует обсуждать данный фрагмент кода . Однако в действительности эти ком- ментарии остаются в коде на долгие годы и со временем становятся все менее точными и актуальными . И снова лучшим источником подобной информации является система контроля исходного кода . Закомментированный код В программировании редко встречаются привычки более отвратительные, чем закрытие комментариями неиспользуемого кода . Никогда не делайте этого! InputStreamResponse response = new InputStreamResponse(); response.setBody(formatter.getResultStream(), formatter.getByteCount()); // InputStream resultsStream = formatter.getResultStream(); // StreamReader reader = new StreamReader(resultsStream); // response.setContent(reader.read(formatter.getByteCount())); У других программистов, видящих закомментированный код, не хватает хра- брости удалить его . Они полагают, что код оставлен не зря и слишком важен для удаления . В итоге закомментированный код скапливается, словно осадок на дне бутылки плохого вина . Следующий код взят из общих модулей Apache: this.bytePos = writeBytes(pngIdBytes, 0); // hdrPos = bytePos; writeHeader(); writeResolution(); // dataPos = bytePos; if (writeImageData()) { writeEnd(); this.pngBytes = resizeByteArray(this.pngBytes, this.maxPos); } else { this.pngBytes = null; } return this.pngBytes; Почему эти две строки кода закомментированы? Они важны? Их оставили как напоминание о будущих изменениях? Или это «хлам», который кто-то закоммен- тировал сто лет назад и не удосужился убрать из программы? В 60-е годы закомментированный код мог быть действительно полезен . Но с тех пор у нас давно появились хорошие системы контроля исходного кода . Эти систе- мы запоминают изменения в коде за нас . Нам уже не нужно закрывать их коммен- тариями . Просто удалите ненужный код . Он никуда не исчезнет . Честное слово . 95 96 Глава 4 . Комментарии Комментарии hTmL Как видно из следующего фрагмента, HTML в комментариях к исходному коду выглядит отвратительно . Он затрудняет чтение комментариев именно там, где они должны легко читаться — в редакторе/IDE . Если комментарии должны извлекаться внешним инструментом (например, Javadoc) для отображения в веб- странице, то за украшение комментариев соответствующим кодом HTML должен отвечать этот инструмент, а не программист . /** * Задача для запуска тестов. * Задача запускает тесты fitnesse и публикует результаты. * * * Usage: * <taskdef name="execute-fitnesse-tests" * classname="fitnesse.ant.ExecuteFitnesseTestsTask" * classpathref="classpath" /> * OR * <taskdef classpathref="classpath" * resource="tasks.properties" /> * * <execute-fitnesse-tests * suitepage="FitNesse.SuiteAcceptanceTests" * fitnesseport="8082" * resultsdir="${results.dir}" * resultshtmlpage="fit-results.html" * classpathref="classpath" /> * */ нелокальная информация Если вы должны написать комментарий, проследите за тем, чтобы он описывал находящийся поблизости код . Не излагайте информацию системного уровня в контексте локального комментария . Примером служит приведенный ниже комментарий Javadoc . Не считая того факта, что комментарий ужасающе из- быточен, в него также включена информация о порте по умолчанию, притом что функция никоим образом не может управлять этим значением . И конечно, ничто не гарантирует, что комментарий будет изменен при изменении кода, в котором это значение определяется . /** * Порт, на котором будет работать fitnesse. По умолчанию 8082. * * @param fitnessePort */ public void setFitnessePort(int fitnessePort) { this.fitnessePort = fitnessePort; } 96 Плохие комментарии 97 Слишком много информации Не включайте в комментарии интересные исторические дискуссии или опи- сания подробностей, не относящиеся к делу . Следующий комментарий был извлечен из модуля, который должен был проверять, что функция кодирует и декодирует данные в формате base64 . Читателю кода совершенно не нужна заумная информация, содержащаяся в этом комментарии, — вполне достаточно номера RFC . /* RFC 2045 - Multipurpose Internet Mail Extensions (MIME) Часть 1: Формат тел сообщений раздел 6.8. Кодирование данных Base64 В процессе кодирования 24-разрядные группы входных битов представляются в виде выходных строк из 4 закодированных символов. Слева направо 24-разрядная входная группа образуется посредством конкатенации 38-разрядных входных групп. Далее эти 24 бита интерпретируются как 4 конкатенированных 6-разрядных группы, каждая из которых преобразуется в одну цифру алфавита base64. При кодировании потока битов в кодировке base64 предполагается, что битовый поток упорядочивается от старшего значащего бита. Иначе говоря, первым битом потока будет старший бит первого 8-битового байта, а восьмым - младший бит первого 8-битого байта и т. д. */ неочевидные комментарии Связь между комментарием и кодом, который он описывает, должна быть оче- видной . Если уж вы берете на себя хлопоты, связанные с написанием коммента- рия, то по крайней мере читатель должен посмотреть на комментарий и на код и понять, о чем говорится в комментарии . Для примера возьмем следующий комментарий из общих модулей Apache: /* * Начать с массива, размер которого достаточен для хранения * всех пикселов (плюс байты фильтра), плюс еще 200 байт * для данных заголовка */ this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200]; Что такое «байты фильтра»? Они как-то связаны с +1? Или с *3? И с тем и с дру- гим? Один пиксел соответствует одному байту? И почему 200? Цель коммента- рия — объяснить код, который не объясняет сам себя . Плохо, когда сам коммен- тарий нуждается в объяснениях . Заголовки функций Короткие функции не нуждаются в долгих описаниях . Хорошо выбранное имя компактной функции, которая выполняет одну операцию, обычно лучше заго- ловка с комментарием . 97 98 Глава 4 . Комментарии Заголовки Javadoc во внутреннем коде При всей полезности комментариев Javadoc для API общего пользования не применяйте их в коде, не предназначенном для общего потребления . Генериро- вание страниц Javadoc для внутренних классов и функций системы обычно не приносит реальной пользы, а формализм комментариев Javadoc только отвлекает читателя . Пример Модуль в листинге 4 .7 был написан для первого учебного курса «XP Immersion» . Предполагалось, что он является примером плохого кодирования и стиля ком- ментирования . Кент Бек переработал этот код в куда более приятную форму перед несколькими десятками увлеченных слушателей . Позднее я приспособил этот пример для своей книги «Agile Software Development, Principles, Patterns, and Practices» и статьи в журнале «Software Development» . Любопытно, что в то время многие из нас считали этот модуль «хорошо документированным» . Теперь мы видим, что он представляет собой ералаш . Посмотрим, сколько разных ошибок комментирования вам удастся найти . листинг 4 .7 . GeneratePrimes.java /** * Класс генерирует простые числа в диапазоне до максимального значения, * заданного пользователем, по алгоритму "Решета Эратосфена". * * Эратосфен Киренский, 276 год до н.э., Ливия -- * 194 год до н.э., Александрия. * Первый ученый, вычисливший длину земного меридиана. Известен своими работами * о календарях с високосным годом, заведовал Александрийской библиотекой. * * Алгоритм весьма прост. Берем массив целых чисел, начиная с 2, и вычеркиваем * из него все числа, кратные 2. Находим следующее невычеркнутое число * и вычеркиваем все его кратные. Повторяем до тех пор, пока не дойдем * до квадратного корня верхней границы диапазона. * * @author Альфонс * @version 13 февраля 2002 u */ import java.util.*; public class GeneratePrimes { /** * @param maxValue - верхняя граница диапазона. */ public static int[] generatePrimes(int maxValue) { if (maxValue >= 2) // Единственно допустимый случай 98 Плохие комментарии 99 { // Объявления int s = maxValue + 1; // Размер массива boolean[] f = new boolean[s]; int i; // Инициализировать массив значениями true. for (i = 0; i < s; i++) f[i] = true; // Удалить числа, заведомо не являющиеся простыми. f[0] = f[1] = false; // Отсев int j; for (i = 2; i < Math.sqrt(s) + 1; i++) { if (f[i]) // Если элемент i не вычеркнут, вычеркнуть кратные ему. { for (j = 2 * i; j < s; j += i) f[j] = false; // Кратные числа не являются простыми. } } // Сколько простых чисел осталось? int count = 0; for (i = 0; i < s; i++) { if (f[i]) count++; // Приращение счетчика } int[] primes = new int[count]; // Переместить простые числа в результат for (i = 0, j = 0; i < s; i++) { if (f[i]) // Если простое primes[j++] = i; } return primes; // Вернуть простые числа } else // maxValue < 2 return new int[0]; // Вернуть пустой массив при недопустимых входных данных. } } В листинге 4 .8 приведена переработанная версия того же модуля . Обратите вни- мание: применение комментариев стало намного более ограниченным . Во всем модуле осталось всего два комментария пояснительного характера . 99 |