Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Джош Блох

.pdf
Скачиваний:
57
Добавлен:
08.03.2016
Размер:
27.13 Mб
Скачать

Глава 8 Общие вопросы программирования

ледованному коду. Например, интерфейс JD B C (Java DataBase Connectivity) обеспечивает доступ к унаследованным базам данных.

В версии 1.3 использование машинозависимых методов для повышения производительности редко оправдывает себя. В пре­ дыдущих версиях это часто было необходимо, однако сейчас созданы более быстрые реализации JV M . И теперь для большинства задач можно получить сравнимую производительность, не прибегая к ма­ шинозависимым методам. Например, когда в версию 1.1 был вклю­ чен пакет java, math, Biglnteger, он был реализован поверх быстрой библиотеки арифметических операций с многократно увеличенной точностью, написанной на языке С. В то время это было необходимо для получения приемлемой производительности. В версии 1.3 класс Biglnteger полностью переписан на языке Java и тщательно отре­ гулирован. Для большинства операций и размеров операндов новая версия оказывается быстрее первоначальной во всех реализациях JV M 1.3 компании Sun.

Применение машинозависимых методов имеет серьезные недостат­ ки. Поскольку машинозависимые методы небезопасны (статья 39), использующие их приложения теряют устойчивость к ошибкам, свя­ занным с памятью. Для каждой новой платформы машинозависимый программный код необходимо компилировать заново, может потре­ боваться даже его изменение. С переходом на машинозависимый код и с возвратом в Java связаны высокие накладные расходы, а потому, если машинозависимые методы выполняют лишь небольшую работу, их применение может снизить производительность приложения. Н а­ конец, машинозависимые методы сложно писать и трудно читать.

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

320

С татья 55

Соблюдайте осторожность при оптимизации

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

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

Уильям Вульф (William A. Wulf) [Wulf72]

Мы обязаны забывать о мелких усовершенствованиях, ска­ жем, на 97% рабочего времени: опрометчивая оптимизация — корень всех зол.

Дональд Кнут (Donald Е. Knuth) [Knuth74]

Что касается оптимизации, то мы следуем двум правилам: Правило 1. Не делайте этого.

Правило 2 (только для экспертов) . Пока не делайте этого — т.е. пока у вас нет абсолютно четкого, но неоптимизированного решения.

М. А. Джексон (М. A. Jackson) [Jackson75]

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

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

321

Глава 8 Общие вопросы программирования

граммы воплощают принцип сокрытия информации (information hiding): по возможности они локализуют конструкторские решения в отдельных модулях, а потому отдельные решения можно менять, не затрагивая остальные части системы (статья 13).

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

Старайтесь избегать конструкторских решений, ограничи­ вающих производительность. Труднее всего менять те компонен­ ты, которые определяют взаимодействие модулей с окружающим ми­ ром. Главными среди таких компонентов являются A PI, протоколы физического уровня и форматы записываемых данных. Мало того, что эти компоненты впоследствии сложно или невозможно менять, любой из них способен существенно ограничить производительность, которую можно получить от системы.

Изучите влияние на производительность тех проектных ре­ шений, которые заложены в ваш API. Создание изменяемого от­ крытого типа может потребовать создания множества ненужных ре­ зервных копий (статья 39). Точно так же использование наследования в открытом классе, для которого уместнее была бы композиция, на­ всегда привязывает класс к его суперклассу, а это может искусствен­ но ограничивать производительность данного подкласса (статья 16). И последний пример: указав в API не тип интерфейса, а тип реализую­ щего его класса, вы оказываетесь привязаны к определенной реализа­ ции этого интерфейса, даже несмотря на то, что в будущем, возможно, будут написаны еще более быстрые его реализации (статья 32).

322

С тать я 55

Влияние архитектуры A PI на производительность велико. Рас­ смотрим метод getSize из класса java.awt.Component. То, что этот критичный для производительности метод возвращает экземпляр Dimension, а также то, что экземпляры Dimension являются изменяе­ мыми, приводит к тому, что любая реализация этого метода при ка­ ждом вызове создает новый экземпляр Dimension. И хотя, начиная с версии 1.3, создание небольших объектов обходится относительно дешево, бесполезное создание миллионов объектов может нанести производительности приложения реальный ущерб.

В данном случае имеется несколько альтернатив. В идеале класс Dimension должен стать неизменяемым (статья 13). Либо метод getSize можно заменить двумя методами, возвращающими отдель­ ные простые компоненты объекта Dimension. И действительно, с це­ лью повышения производительности в версии 1.2 два таких метода были добавлены в интерфейс класса Component. Однако уже суще­ ствовавший к тому времени клиентский код продолжает пользовать­ ся методом getSize, и его производительность по-прежнему страдает от первоначально принятых проектных решений для API.

Хорошая схема API, как правило, сочетается с хорошей произво­ дительностью. Не стоит искажать API ради улучшения произво­ дительности. Проблемы с производительностью, которые заставили вас переделать A PI, могут исчезнуть с появлением новой платформы или других базовых программ, а вот искаженный API и связанная с ним головная боль останутся с вами навсегда.

После того как вы тщательно спроектировали программу и вы­ строили четкую, краткую и хорошо структурированную ее реали­ зацию, можно подумать об оптимизации, если, конечно, вы еще не удовлетворены производительностью программы. Напомним два правила Джексона: «не делайте этого» и «не делайте этого пока (для экспертов)». Он мог бы добавить еще одно: измеряйте производи­ тельность до и после попытки ее оптимизации.

Возможно, вы будете удивлены, но нередко попытки оптимиза­ ции не оказывают поддающегося измерению влияния на производи­

323

Глава 8 Общие вопросы программирования

тельность, иногда они даже ухудшают ее. Основная причина заклю­ чается в том, что сложно догадаться, где программа теряет время. Та часть программы, которую вы считаете медленной, может ока­ заться ни при чем, и вы зря потратите время, пытаясь ее оптимизи­ ровать. Общее правило гласит, что 80% времени программы теряют на 20% своего кода.

Средства профилирования помогут вам определить, где именно следует сосредоточить усилия по оптимизации. Подобные инстру­ менты предоставляют вам информацию о ходе выполнения програм­ мы, например: сколько примерно времени требуется каждому методу, сколько раз он был вызван. Это укажет вам объект для настройки, а также может предупредить вас о необходимости замены самого алгоритма. Если в вашей программе скрыт алгоритм с квадратичной (или еще худшей) зависимостью, никакие настройки эту проблему не решат. Следовательно, вам придется заменить алгоритм более эф­ фективным. Чем больше в системе программного кода, тем большее значение имеет работа с профилировщиком. Это все равно что искать иголку в стоге сена: чем больше стог, тем больше пользы от металло­ искателя. JD K поставляется с простым профилировщиком, несколь­ ко инструментов посложнее можно купить отдельно.

Задача определения эффекта оптимизации для платформы Java стоит острее, чем для традиционных платформ, по той причине, что язык программирования Java не имеет четкой модели производитель­ ности (performance model). Нет четкого определения относительной стоимости различных базовых операций. «Семантический разрыв» между тем, что пишет программист, и тем, что выполняется централь­ ным процессором, здесь гораздо значительнее, чем у традиционных компилируемых языков, и это сильно усложняет надежное предсказа­ ние того, как будет влиять на производительность какая-либо оптими­ зация. Существует множество мифов о производительности, которые на поверку оказываются полуправдой, а то и совершенной ложью.

Помимо того, что модель производительности плохо определена, она меняется от одной реализации JV M к другой и даже от версии

324

С татья 56

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

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

При выборе имен придерживайтесь общепринятых соглашений

Платформа Java обладает хорошо устоявшимся набором со­ глашений, касающихся выбора имен (naming convention). Многие из них приведены в « The Java Language Specification» [JLS, 6.8]. Соглашения об именовании делятся на две категории: типографские и грамматические.

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

325

Глава 8 Общие вопросы программирования

мистов, работающих с этим кодом, а также способствовать появле­ нию ложных допущений, приводящих к ошибкам.

Названия пакетов должны представлять собой иерархию, от­ дельные части которой отделены друг от друга точкой. Эти части должны состоять из строчных букв и изредка цифр. Название любо­ го пакета, который будет использоваться за пределами организации, обязано начинаться с доменного имени вашей организации в Интер­ нете, которому предшествуют домены верхнего уровня, например edu. emu, com. sun, gov. nsa. Исключение из этого правила составляют стандартные библиотеки, а также необязательные пакеты, чьи назва­ ния начинаются со слов java и javax. Пользователи не должны соз­ давать пакетов с именами, начинающимися с java или javax. Деталь­ ное описание правил, касающихся преобразования названий доменов Интернета в префиксы названий пакетов, можно найти в «The Java Language Specification» [JLS, 7.7].

Вторая половина в названии пакета должна состоять из одной или нескольких частей, описывающих этот пакет. Части должны быть короткими, обычно не длиннее восьми символов. Поощряются выразительные сокращения, например util вместо utilities. Допу­ стимы акронимы, например awt. Такие части, как правило, должны состоять из одного-единственного слова или сокращения.

Многие пакеты имеют имена, в которых, помимо названия до­ мена в Интернете, присутствует только одно слово. Большее коли­ чество частей в имени пакета нужно лишь для больших систем, чей размер настолько велик, что требует создания неформальной иерар­ хии. Например, в пакете javax. swing представлена сложная иерархия пакетов с такими названиями, как javax. swing, plat, metal. Подоб­ ные пакеты часто называют подпакетами, однако это относится ис­ ключительно к области соглашений, поскольку для иерархии пакетов нет лингвистической поддержки.

Названия классов и интерфейсов состоят из одного или не­ скольких слов, причем в каждом слове первая буква должна быть заглавной, например Timer или TimerTask. Необходимо избегать аб­ бревиатур, за исключением акронимов и нескольких общепринятых

326

С татья 56

сокращений, таких как max и min. Нет полного единодушия по поводу того, должны ли акронимы полностью писаться прописными буквами или же заглавной у них должна быть только первая буква. Хотя чаще в верхнем регистре пишется все название, есть один сильный аргу­ мент в пользу того, чтобы заглавной была только первая буква. В по­ следнем случае всегда ясно, где кончается одно слово и начинается другое, даже если рядом стоят несколько акронимов. Какое название класса вы предпочли бы увидеть: HTTPURL или HttpUrl?

Названия методов и полей подчиняются тем же самым типограф­ ским соглашениям, за исключением того, что первый символ в назва­ нии всегда должен быть строчным, например remove, ensu reCapacity. Если первым словом в названии метода или поля оказывается акро­ ним, он весь пишется строчными буквами.

Единственное исключение из предыдущего правила касается по­ лей-констант (constant field), названия которых должны состоять из одного или нескольких слов, написанных заглавными буквами и от­ деленных друг от друга символом подчеркивания, например VALUES или

NEGATIVE_INFINITY. Поле-константа — это поле static final, значе­

ние которого не меняется. Если поле static final имеет простой тип или неизменяемый ссылочный тип (статья 15), то это поле-константа. Например, перечислимые константы являются полями-константами. Если у статического завершенного поля есть изменяемый тип ссылки, оно все еще может быть полем-константой, если объект, на который оно ссылается, является неизменяемым. Заметим, что поля-констан- ты — это единственное место, где допустимо использование символа подчеркивания. Заметим, что поля-константы — единственное ме­ сто, где допустимо использование символа подчеркивания.

Названия локальных переменных подчиняются тем же типо­ графским соглашениям, что и названия членов классов, за исключе­ нием того, что в них можно использовать аббревиатуры, отдельные символы, а также короткие последовательности символов, смысл которых зависит от того контекста, где эти локальные переменные находятся. Например: i, xref, houseNumber. Наименование типа па­ раметров обычно состоит из одной буквы.

327

Глава 8 Общие вопросы программирования

Наиболее часто это одно из этих пяти: Т для произвольных типов, Е для типов элементов в коллекции, К и V для ключей и типов значе­ ний схемы, и X для исключений. Последовательность произвольных типов может быть Т, U, V или Т1, Т2, ТЗ.

Примеры типографских соглашений приведены в таблице 7.1.

Таблица 7.1

Примеры типографских соглашений

Tim идентификатора

Примеры

П ак ет

com .google.inject, org.jode.tim e.form at

К ласс или интерфейс

Tim er, FutureTask, L in ked H ash M ap, H ttpServlet

М етод или поле

remove, ensureCapacity, getC rc

П оле-константа

M I N _ V A L U E S , N E G A T I V E J N F I N I T Y

Л окальная переменная

i, xref, houseN um ber

Тип параметра

T , E , К , V , X , T l , T 2

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

например Timer и BufferedWriter или ChessPiece. Интерфейсы име­

нуются так же, как классы, например Collection и Comparator, либо применяются названия с окончаниями, образованными от прилага­ тельных «-able» и «-ible», например Runnable и Accessible. Посколь­ ку у типов аннотаций есть много вариантов использования, то тут нет преобладания каких-либо частей речи. Существительные, глаголы, предлоги и прилагательные используются в равной степени, напри­

мер BindingAnnotation, Inject, ImplementedBy или Singleton.

Для методов, выполняющих какое-либо действие, в качестве на­ звания используются глаголы или глагольные конструкции, например append и drawlmage. Для методов, возвращающих булево значение,

328

С татья 56

обычно применяются названия, в которых сначала идет слово «is», а потом существительное, именная конструкция или любое слово (фраза), играющее роль прилагательного, например isDigit, isProb-

ablePrime, isEmpty, isEnabled, isRunning.

Для именования методов, не связанных с булевыми операция­ ми, а также методов, возвращающих атрибут объекта, для которого они были вызваны, обычно используется существительное, именная конструкция либо глагольная конструкция, начинающаяся с глагола «get», например size, hashCode, getTime. Отдельные пользователи требуют, чтобы применялась лишь третья группа (начинающаяся с «get»), но для подобных претензий нет никаких оснований. Пер­ вые две формы обычно делают текст программы более удобным для чтения, например:

if (car.speedO > 2* SPEED.LIMIT) generateAudibleAlert("Watch out for cops!”);

Форма, начинающаяся c «get», обязательна, если метод принад­ лежит к классу Bean [JavaBeans]. Ее можно также рекомендовать, если в будущем вы собираетесь превратить свой класс в Bean. Нако­ нец, серьезные основания для использования данной формы имеются в том случае, если в классе уже есть метод, присваивающий этому же атрибуту новое значение. При этом указанные методы следует

назвать getAttribute и setAttribute.

Несколько названий методов заслуживают особого упомина­ ния. Методы, которые преобразуют тип объекта и возвращают не­ зависимый объект другого типа, часто называются toType, например toString, toArray. Методы, которые возвращают п р ед ст а вл ен и е (статья 4), имеющее иной тип, чем сам объект, обычно называются asType, например asList. Методы, возвращающие простой тип с тем же значением, что и у объекта, в котором они были вызваны, называ­ ются typeValue, например intValue. Для статических методов генера­ ции широко используются названия valueOf и getlnstance (статья 1).

Грамматические соглашения для названий полей формализованы в меньшей степени и не играют такой большой роли, как в случае

329

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]