- PVSM.RU - https://www.pvsm.ru -

Java на каждый день и не только. Рекомендации по использованию

Всем привет!

Вашему вниманию предлагается перевод статьи уже известного на Хабре [1] автора. На этот раз он делится своими видением того, как часто нужно применять в своей повседневной разработке те или иные свойства языка Java.


image [2]
Java — это язык с мощными стандартными возможностями, но «Большая сила налагает большую ответственность» [3]. Я видел много java-кода, в котором чрезмерно (и зачастую — неправильно) использовались «редкие» свойства языка, в то время как основы основ были почти полностью проигнорированы. Эти наблюдения и послужили стимулом к написанию статьи.

Это не список обязательных к использованию каждым программистом особенностей языка. Скорее наоборот. Я разделил их на 3 группы: "для каждодневного использования", "для периодического использования" и "только для фреймворков и библиотек!". Правило простое: если вы понимаете, что используете указанные свойства чаще, чем рекомендуется, то, скорее всего, ваш код развивается по неправильному пути. Если же наоборот — вы редко используете какие-то свойства, чем я рекомендую, значит вы упускаете какие-то интересные и важные возможности языка.

Обратите внимание, что я говорю о разработке типичных серверных бизнес-приложений (JVM, JDK, вот это все) и не даю рекомендаций относительно каких бы то ни было фреймворков.

Для каждодневного использования

Классы, интерфесы, пакеты
Да-да, размещайте свой код в классах. Ещё со времени учебы вы наверяка помните, что класс — это данные и методы для работы с этими данными. Класс, который содержит только данные, называется «структурой». Класс, в котором только лишь методы, по сути просто логически объединяет некоторую функциональность. Используйте интерфейсы там, где это необходимо. Но подумайте дважды перед тем как создать интерфейс с одной единственной реализацией. Может стоит избавиться от посредника? Ну и наконец — размещайте классы и интерфейсы в пакетах, не забывая следовать соглашениям об именовании [4].

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

ExecutorService, thread pool
Нужно обязательно понимать и правильно использовать пулы потоков [5], очередей и объектов типа Future<T> [6] Не изобретайте велосипедов, реализуя собственные пулы. Они должны в первую очередь приходить вам в голову, когда вы слышите о Producer-Consumer

Семейство Atomic-*
Не используйте synchronized для чтения/изменения счетчиков и ссылок. Семейство Atomic-* весьма эффективно реализовано на основе «сравнения с обменом» [7] Убедитесь, что вы понимаете гарантии, которые предоставляют эти классы.

Шаблоны проектирования
Технически они не являются частью языка Java, но я обязан упомянуть о них в виду их важности. Вы должны знать, понимать и свободно использовать их, но в разумных пределах. (Так же как и интерфейсы, к слову). Шаблоны «Банды четырёх» [8] и корпоративных приложений [9] должны быть широко представлены в вашем коде. Но их использование должно подчиняться нуждам вашего приложения, а не наоборот.

Стандартные коллекции, в том числе многопоточные
Вам абсолютно необходимо знать и использовать встроенные коллекции, понимать разницу между List, Map и Set. Использование потокобезопасных реализаций также не должно быть проблемой. Нужно знать основные характеристики производительности и иметь представление о деталях реализаций. Добавим сюда же знание различных реализаций BlockingQueue [10]. Параллельные вычисления и без того сложны, нужно пользоваться имеющимися средствами языка, а не изобретать свои велосипеды.

Встроенные аннотации
Они уже здесь и это надолго, поэтому смело пользуйтесь @Override [11] и не забывайте про @Deprecated [12]

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

try-with-resources
Познакомьтесь с этой замечательной конструкцией [13]. Реализуйте AutoCloseable [14], если вашему классу требуется освобождать ресурсы после завершения своей работы.

Блокирующий ввод/вывод
Пользуйтесь классами Reader [15]/Writer [16], InputStream [17]/OutputStream [18]. Осознавайте в чем отличия между ними, смело используйте буферизацию и другие декораторы.

На этом список «на каждый день» заканчивается. Если вы о чем-то не слышали вовсе или слышали краем уха — изучите, вам это обязательно пригодится.

Для периодического использования

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

Наследование и абстрактные классы
Честно, я редко пользуюсь наследованием и не скажу, что сильно скучаю по нему. Полиморфизм весьма гибко реализуется на основе интерфейсов, особенно с учетом всех недостатков абстракции в Java(* [19]) Также я предпочитаю наследованию композицию [20]. Слишком большие иерархии порождают трудно поддерживаемый код
image [21]

Регулярные выражения
Некоторые программисты, когда встречаются с проблемой, думают «О, я воспользуюсь регулярными выражениями!» И получают две проблемы. [22] Мир без регулярных выражений был бы гораздо более скучным и громоздким. Они прекрасно подходят для парсинга регулярных множеств [23] (кроме HTML [24]), но опять же — с ними очень легко переусердствовать. Если вы целыми днями оперируете регулярными выражениями, вы выбрали неправильный инструмент. Хит всех времен:

public static boolean isNegative(int x) {
    return Integer.toString(x).matches("-[0-9]+");
}

Semaphore [25], CountDownLatch [26], CyclicBarrier [27] и т.д.
Эти классы, конечно, на порядок более полезные, чем печально известная парочка wait()/notify(). Но и они не защищают вас от ошибок при использовании многопоточности. Если вы часто используете этот механизм для синхронизации — самое время посмотреть в сторону потоко-безопасных коллекций или специализированных фреймворков.

Параметризованные типы (generics) в пользовательском коде
Вообще использование встроенных коллекций и других типов данных, поддерживающих параметризацию — обычное дело для программиста. Но здесь я говорю об использовании параметризации в вашем собственном коде, когда ваши методы принимают или возвращают generic types. Например:

public <T, F> ContractValidator<T extends Contract> T validate(Validator<T>, F object)

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

Скриптовые языки в JVM
Знаете ли вы, что JDK имеет встроенный интерпретатор JavaScript [28]? И что вы можете на лету подключить другие языки, вроде Groovy и JRuby? В век стремительно меняющихся рынков порой не хватает времени даже на то, чтобы передеплоить приложение, и внедрение небольшого скрипта с возможностью его редактирования конечным пользователем бывает хорошей идеей. Но помните, что если количество скриптов превышает 1% от общего количества строк в системе, вам нужно быть готовым к трудностям при поддержке.

Java NIO [29]
Сложно использовать его правильно и ещё сложнее действительно получить от него выгоду. Иногда вы должны его использовать, чтобы выжать максимум по производительности и масштабированию. Но лучше все-таки пользоваться специализированными библиотеками, тем более что в большинстве случаев вполне достаточно обычного ввода-вывода.

synchronized
Не нужно злоупотреблять им по простой причине — чем больше таких блоков, тем чаще они выполняются и тем ниже производительность. Плюс всегда нужно быть хорошо уверенным в том, какой именно объект является мьютексом. Лучше используйте thread-safe коллекции и Atomic-*.

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

Только для разработчиков фреймворков и библиотек!

Для эффективного использования фреймворков и библиотек вы должны понимать принципы перечисленных ниже свойств языка. StackOverflow забит вопросами, ответы на которые можно легко найти в исходном коде самих фреймворков. Но «понимание» не обязательно означает «использование». Все эти штуки — они достаточно низкоуровневые и сложные, и даже в небольших количествах могут доставить немалую головную боль.

сокеты
да-да, вы не ослышались, именно сокеты. Вы должны понимать как работает стек TCP/IP, сессии, потоки, уметь правильно интерпретировать данные. Но избегайте прямой работы на сетевом уровне. Существуют сотни высокоуровневых библиотек для HTTP, FTP, NTP, SMB, e-mail… взять тот же Apache Commons net [30]. Вы вряд ли подозреваете, насколько сложно написать приличный HTTP клиент или сервер. А если вам нужен сервер для какого-то собственного протокола — я рекомендую ознакомиться с Netty [31]

reflection
В прикладном коде нет места работе на уровне внутреннего устройства классов и методов. Это жизненно необходимо фреймворкам, но совершенно не нужно лично мне. Reflection делает ваш код низкоуровневым, небезопасным и просто… неприятным. АОР решает большинство проблем. Я бы даже сказал, что простая работа с экземплярами типа Class<?> уже «плохо пахнет».

динамические прокси и работа с байт-кодом
Proxy [32] — это здорово, но, как и reflection, лучше оставить работу с ним на откуп фреймворкам и библиотекам. Прокси — основа построения легковесного АОР. Если ваше бизнес-приложение непосредственно работает с байт-кодом(** [33]) (например через ASM [34] или CGLIB [35]) — вы в ж*пе мне остается только молиться за вас.

classloaders
… и все что с ними связано — в топку! опять таки — нужно понимать, как они работают, иерархию, байткод и т.д. Но если вы пишете свой загрузчик — это дорога в ад. Не то чтобы это было так сложно, но просто зачем? Пусть этим занимаются сервера приложений.

Object.clone()
Не помню, использовал ли я хоть раз этот метод за всю свою практику. А, вспомнил — совершенно точно ни разу не использовал! И даже не могу придумать, зачем он мне может понадобиться. Предпочитаю явные конструкторы копирования, а ещё лучше — неизменяемые объекты. А вам нужен именно clone? Похоже, кто-то застрял в девяностых…

native методы
В JDK можно найти несколько таких, в частности функцию вычисления синуса [36] Но Java давно уже не тот тугодум, что раньше, даже наоборот. И я не могу придумать, какие задачи не могут решить стандартная или сторонние библиотеки. Плюс, нативные методы трудны сами по себе, порождают множество низкоуровневых ошибок, особенно в части работы с памятью.

самописные коллекции
правильно реализовать коллекцию в соответствии со всеми контрактами оригинального JavaDoc (внезапно) непростая задача [37] Hibernate использует собственные реализации, но зачем они могут понадобиться вам — я не знаю.

ThreadLocal [38]
Фреймворки и библиотеки используют эту технику довольно часто, но вы должны избегать этого по двум причинам. Первая — под маской ThreadLocal часто скрывается эдакая полу-глобальная переменная. Это усложняет понимание и тестирование кода. Вторая — ThreadLocal порождает утечку памяти в случае некоректной очистки. Для примера читаем тут [39], тут [40], тут [41] и ещё вот тут [42] и так далее…

WeakReference [43] и SoftReference [44]
Эти классы достаточно низкоуровневые и хорошо подходят для реализации кэшей, плотно интегрированных со сборщиком мусора. К счастью, существует множество open-source библиотек для подобных кэшей, так что нет нужды писать ещё один самостоятельно. Достаточно просто знать, что такие классы есть и представлять, как они работают.

Пакеты com.sun.* и sun.*, особенно sun.misc.Unsafe [45]
Держитесь подальше от торфяных болот этих пакетов, потому что… да просто подальше и все! Это сугубо специальные, недокументированные классы без гарантии сохранения обратной совместимости в будущем. Просто представьте, что их нет. Да и зачем бы вам мог понадобиться Unsafe [46]?

Собственно, на этом все. Конечно, все это моё абсолютнейшее ИМХО. Если вы чувствуете, что что-то не на своем месте, либо я забыл о чем-то существенном — прошу в комментарии, чтобы в дальнейшем я мог составить некое справочное руководство для проведения code-review или для помощи в оценке проекта.


(*) приветствуется более точный перевод выражения "a painful lack of traits in Java"
Mocito [47] я тоже не осилил

Автор: monzdrpower

Источник [48]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/java/28001

Ссылки в тексте:

[1] известного на Хабре: http://habrahabr.ru/search/?q=Nurkiewicz

[2] Image: http://1.bp.blogspot.com/-d114xzObirE/UI2i1VPARNI/AAAAAAAAAog/ipIpS0hLNfE/s1600/triangle.png

[3] «Большая сила налагает большую ответственность»: http://en.wikipedia.org/wiki/Uncle_Ben#.22With_great_power_comes_great_responsibility.22

[4] соглашениям об именовании: http://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html

[5] пулы потоков: http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html

[6] Future<T>: http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html

[7] «сравнения с обменом»: http://ru.wikipedia.org/wiki/%D0%A1%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5_%D1%81_%D0%BE%D0%B1%D0%BC%D0%B5%D0%BD%D0%BE%D0%BC

[8] «Банды четырёх»: http://www.amazon.com/gp/product/0201633612/ref=as_li_ss_tl?ie=UTF8&tag=javaandneighb-20&linkCode=as2&camp=1789&creative=390957&creativeASIN=0201633612

[9] корпоративных приложений: http://rcm.amazon.com/e/cm?lt1=_blank&bc1=000000&IS2=1&bg1=FFFFFF&fc1=000000&lc1=0000FF&t=javaandneighb-20&o=1&p=8&l=as4&m=amazon&f=ifr&ref=ss_til&asins=0321200683

[10] BlockingQueue: http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/BlockingQueue.html

[11] @Override: http://docs.oracle.com/javase/7/docs/api/java/lang/Override.html

[12] @Deprecated: http://docs.oracle.com/javase/7/docs/api/java/lang/Deprecated.html

[13] этой замечательной конструкцией: http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

[14] AutoCloseable: http://docs.oracle.com/javase/7/docs/api/java/lang/AutoCloseable.html

[15] Reader: http://docs.oracle.com/javase/7/docs/api/java/io/Reader.html

[16] Writer: http://docs.oracle.com/javase/7/docs/api/java/io/Writer.html

[17] InputStream: http://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html

[18] OutputStream: http://docs.oracle.com/javase/7/docs/api/java/io/OutputStream.html

[19] *: #note1

[20] наследованию композицию: http://en.wikipedia.org/wiki/Composition_over_inheritance

[21] Image: http://1.bp.blogspot.com/-Qh7Yx5wvkEg/UI2i0dHdHhI/AAAAAAAAAoc/ezF1j5UUpT4/s1600/spring.png

[22] Некоторые программисты, когда встречаются с проблемой, думают «О, я воспользуюсь регулярными выражениями!» И получают две проблемы.: http://en.wikiquote.org/wiki/Jamie_Zawinski

[23] регулярных множеств: http://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D0%BE%D0%B5_%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%

[24] кроме HTML: http://stackoverflow.com/a/1732454

[25] Semaphore: http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Semaphore.html

[26] CountDownLatch: http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html

[27] CyclicBarrier: http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html

[28] JDK имеет встроенный интерпретатор JavaScript: http://docs.oracle.com/javase/6/docs/technotes/guides/scripting/programmer_guide/index.html

[29] Java NIO: http://docs.oracle.com/javase/7/docs/api/java/nio/package-summary.html

[30] Apache Commons net: http://commons.apache.org/net/

[31] Netty: https://netty.io/

[32] Proxy: http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html

[33] **: #note2

[34] ASM: http://asm.ow2.org/

[35] CGLIB: http://cglib.sourceforge.net/

[36] функцию вычисления синуса: http://docs.oracle.com/javase/7/docs/api/java/lang/StrictMath.html#sin(double)

[37] непростая задача: http://stackoverflow.com/questions/12761532

[38] ThreadLocal: http://docs.oracle.com/javase/7/docs/api/java/lang/ThreadLocal.html

[39] тут: https://jira.mongodb.org/browse/JAVA-130

[40] тут: http://stackoverflow.com/questions/5292349

[41] тут: https://issues.apache.org/jira/browse/AXIS-935

[42] тут: http://jira.qos.ch/browse/LOGBACK-450

[43] WeakReference: http://docs.oracle.com/javase/7/docs/api/java/lang/ref/WeakReference.html

[44] SoftReference: http://docs.oracle.com/javase/7/docs/api/java/lang/ref/SoftReference.html

[45] sun.misc.Unsafe: http://www.docjar.com/docs/api/sun/misc/Unsafe.html

[46] зачем бы вам мог понадобиться Unsafe: http://stackoverflow.com/questions/5574241

[47] Mocito: http://code.google.com/p/mockito/

[48] Источник: http://habrahabr.ru/post/167291/