Теория и практика Java: Мне нужно задокументировать ЭТО?

Благо и бремя интегрированной документации а ля Javadoc

Язык Java использует интегрированный подход к API-документации с помощью Javadoc comment convention. Инструмент Javadoc может помочь сгенерировать прекрасную API-документацию, но подавляющее большинство Java API-документации за пределами Javadoc является просто ужасной. Поскольку она является частью исходного кода, вся ответственность за документацию API падает целиком на инженера. В данной статье Брайан разражается тирадой о текущем состоянии работы с документацией Java и дает рекомендации по написанию более практичной Javadoc.

Брайан Гетц, главный консультант, Quiotix

Брайан Гетц (Brian Goetz) - консультант по ПО и последние 15 лет работал профессиональными разработчиком ПО. Сейчас он является главным консультантом в фирме Quiotix, занимающейся разработкой ПО и консалтингом и находящейся в Лос-Альтос, Калифорния. Следите за публикациями Брайана в популярных промышленных изданиях. Вы можете связаться с Брайаном по адресу brian@quiotix.com



09.02.2007

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

Обучение от Javadoc

Для многих Java-функций, включая большинство пакетов с открытым исходным кодом и внутренних компонентов от производителей, реальность такова, что очень мало библиотек классов или компонентов поступают вместе со сколько-нибудь стоящей документацией, кроме Javadoc. Это значит, что разработчикам надо будет научиться использовать функции из Javadoc, и мы также должны принять это во внимание, организуя нашу Javadoc. Я часто шутил, что одним из наиболее важных умений для Java-программиста сегодня является квалифицированное использование Google и Javadoc для обратного проектирования плохо задокументированных API. Возможно, это и не далеко от истины, но совсем не весело.

Большинство Java-пакетов имеют какой-либо "корневой" объект, первый объект, который вам необходимо создать, прежде чем вы сможете добраться до любого другого объекта этой функции. В JNDI данный корневой объект - это Context, а в JMS и JDBC - это Connection. Если бы кто-нибудь сказал вам, что фундаментальный объект в JDBC - это Connection, и объяснил, как его получить, вы могли бы узнать из Javadoc, как затем создать и выполнить Statement и выполнить итерацию полученного ResultSet путем внимательного прочтения списка доступных методов в Javadoc. Но как узнать, что первым вашим действием было получение Connection? Javadoc организует классы в пакете и методы в классе в алфавитном порядке. К сожалению, не существует волшебного знака "Start Here (Начинать Здесь)" в Javadoc для привлечения читателей к логическому месту начала, с которого надо исследовать API.

Описание пакета

Наибольшим приближением к знаку "Start Here (Начинать Здесь)" является описание пакета, но оно редко когда используется эффективно. Если вы разместите файл package.html с исходным кодом для пакета, стандартный doclet положит содержимое в сгенерированный файл package-summary.html вместе с перечнем классов в данном пакете. К сожалению, стандартный doclet, выпускающий HTML документацию, которую мы все знаем, не позволяет легко найти описание пакета. Когда вы нажимаете на пакет в верхнем левом окне, то оно выдает список метода в нижнем левом окне, но не выводит резюме пакета в основном окне - вам придется нажать на имя пакета в нижнем левом окне, чтобы его увидеть. Но как бы там ни было, большинство пакетов не имеют описания.

Документация пакета является прекрасным местом для размещения документации "Start Here" в качестве обзора того, что пакет делает, каковы ключевые абстрактные конструкции, и с чего следует начинать изучать Javadoc-пакета.

Документация класса

Кроме документации пакета, документация классов также может значительно помочь пользователю перемещаться по новой функции. Документация классов должна, конечно же, включать описание того, что выполняет именно этот класс, но также и описание того, как этот класс соотносится с другими классами пакета, и в особенности идентифицировать любые значимые фабричные (factory) классы для данного класса. Например, документация для класса Statement в JDBC должна объяснять, что Statement получается с помощью метода createStatement() класса Connection. Таким образом, если новый пользователь имеет затруднения на странице Statement, он может узнать, что сначала ему необходимо получить Connection. Пакет, использующий это условное обозначение для каждого класса, быстро укажет пользователю на корневой объект, и пользователь сможет разобраться.

Поскольку Javadoc спроектирована для документирования конкретных классов, то зачастую в Javadoc не имеется явного места для размещения примерного кода, иллюстрирующего использование нескольких взаимосвязанных классов вместе. Но если фокусироваться на документации для конкретного класса или метода, мы не сможем поговорить о том, как пакет монтируется. Для многих пользователей было бы очень полезно, если бы имелся простой пример кода, демонстрирующий основное применение, или в документации пакета, или в документации класса для корневого объекта. Например, документация класса Connection могла бы иметь простой пример запроса соединения, создавая предварительное выражение, выполняя это выражение и итерируя полученный результат. Технически это может и не размещаться на странице Connection, поскольку также описываются и другие классы пакета. Тем не менее, особенно при соединении с вышеупомянутой технологией создания ссылок на классы, от которых зависит текущий класс, пользователь очень быстро найдет способ создать простой рабочий пример, независимо от организации класса.


Плохая документация == плохой код

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

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


Написание Javadoc является формой просмотра кода

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

Возможности самопромотра документации говорят об особой важности написания Javadoc на раннем этапе процесса разработки, а затем ее периодического обзора по мере развития кода, вместо того, чтобы просто дожидаться завершения кода, и только затем писать документацию (если на это останется время). При такой стратегии, которая весьма распространена, написание документации откладывается до финальной стадии проекта, когда графики поджимают, и сотрудники перегружены работой. И в результате мы слишком часто получаем бесполезную документацию, показанную в Листинге 1, которая создает лишь "иллюзию документации." В ней не сообщается ничего действительно необходимого пользователю, никаких сведений о том, как работает данный класс.

Листинг 1. Типичная бесполезная Javadoc
    /**
      * Represents a command history
      */
    public class CommandHistory {

        /**
         * Get the command history for a given user
         */
        public static CommandHistory getCommandHistory(String user) {
        . . .
        }
    }

Из чего же должна состоять хорошая документация?

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

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

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

Правила Javadoc предоставляют тег @param, позволяющий нам задокументировать то, что означает параметр, в дополнение к его имени и типу. Тем не менее, не все методы соглашаются принять любое значение параметра. Например, в случае, когда допустимо передать нулевое значение (null) любому методу, принимающему параметр объекта без нарушения правил контроля типа, не все методы мягко соглашаются принять нулевое значение. В Javadoc должен быть ясно описан допустимый диапазон значений для параметров; если ожидается, что параметр будет не нулевым, то это должно быть указано, и если ожидаемые значения определенной области, такие как строки определенной длины или целые числа больше ноля, то это тоже должно быть указано. Не все методы тщательно проверяют свои параметры на предмет соответствия; отсутствие проверок соответствия и документации по множеству допустимых вводов запросто могут привести вас к аварийной ситуации.

Коды возврата

Javadoc позволяет легко описать возвращаемое значение, но, как и с параметрами метода, тег @return должен включать подробное описание множества значений, которые могут быть возвращены. Для типов возврата значения объекта будет ли когда-либо выдаваться ноль? Для типов возврата значения целого числа ограничивается ли результат набором известных или неотрицательных значений? Имеют ли какие-либо коды возврата особое значение, как, например, возврат -1 из java.io.InputStream.read() для обозначения конца файла? Используется ли код возврата для обозначения ситуации ошибки, как, например, возврат ноля в случае, если запись в таблице не может быть найдена?

Исключения

Стандартный doclet дублирует конструкцию throws метода, но Javadoc-теги @throws должны быть гораздо более конкретными. Например, NoSuchFileException является подклассом IOException, но большинство методов в java.io декларируются только для выброса IOException. Тем не менее, знание того, что метод может выбросить NoSuchFileException отдельно от других IOException является полезным для вызывающего - и этот метод должен быть включен в Javadoc. Также вы должны указать фактическую ситуацию ошибки, при которой будут выбрасываться различные классы исключений так, чтобы вызывающий знал, какое корректировочное действие предпринять в случае выброса данного исключения. Вы должны задокументировать каждое отмеченное и неотмеченное исключение, которое может выбросить метод, с помощью тега @throws, и задокументировать условия, при которых это исключение будет выброшено.

Входные условия, выходные условия и инварианты

Конечно, вы должны задокументировать влияние метода на состояние объекта. Но вы можете захотеть пойти дальше и описать входные условия метода, выходные условия и инварианты класса. Входное условие (precondition) является ограничением состояния объекта до вызова метода; например, входным условием вызова Iterator.next() является истинность hasMore(). Выходным условием (postcondition) является ограничение состояния объекта после завершения вызова метода, так List не является пустым после вызова add(). Инвариантом является ограничение состояния объекта, которое всегда действует постоянно, как Collection.size() == Collection.toArray().length().

Инструменты Design-by-contract, такие как jContract, позволяют вам указывать входные условия, выходные условия и инварианты класса, используя специальные комментарии, а затем инструменты генерируют дополнительный код для применения данных ограничений. Используете вы инструмент или нет для обеспечения данных вероятностей, документирование этих ограничений дает пользователям представление о том, что они могут безопасно сделать с классом.

Побочные эффекты

Иногда метод дает побочные эффекты, отличные от изменений состояния объекта, такие как изменения состояния взаимосвязанных объектов, JVM или базовой компьютерной платформы. Например, все методы, выполняющие I/O, имеют побочные эффекты. Некоторые побочные эффекты вполне безвредны, например, хранение сведений о количестве запросов, обработанных классом. Другие имеют значительное влияние на производительность и точность программы, как например модифицирование состояния объекта, передаваемого методу, или хранение копии ссылки на этот объект. Такие побочные эффекты как модифицирование состояния взаимосвязанных объектов или хранение ссылок на объекты, переданные как параметры метода, должны быть задокументированы.

Связывание (linkage) метода

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

Безопасность потока

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

Безопасность потока в действительности не есть что-то бинарное; имеются несколько распознаваемых степеней безопасности потока. Задокументировать безопасность потока, или даже определить степень безопасности потока, не всегда легко. Но невозможность сделать это может привести к серьезным проблемам; использование поточно-небезопасных классов в текущих приложениях может вызвать спорадические неисправности, которые часто не проявляются до ввода в действие (когда приложение подвергается нагрузке). А надстройка дополнительной блокировки вокруг уже поточно-безопасных классов может повредить производительности и даже может вызвать взаимоблокировку.

В своей книге, Справочник по эффективному программированию на языке Java (см. Ресурсы) Джош Блох предлагает полезную таксономию для документирования степеней безопасности потока класса. Классы могут быть классифицированы по следующим группам, в порядке снижения безопасности потока: неизменяемые, поточно-безопасные, условно поточно-безопасные, поточно-совместимые, и поточно-неблагоприятные.

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


Выводы

Документирование поведения класса это не просто краткое описание того, что делает каждый метод. Эффективная Javadoc должна содержать описания того:

  • Как классы взаимосвязаны друг с другом
  • Как методы влияют на состояние объекта
  • Как методы сообщают вызывающим устройствам о ситуациях ошибки, и о каких ошибках они могут оповещать
  • Как класс справляется с использованием в многопоточном приложении
  • Домен аргументов метода и множество их возвращаемых значений

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

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=194797
ArticleTitle= Теория и практика Java: Мне нужно задокументировать ЭТО?
publish-date=02092007