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

в 11:03, , рубрики: best practices, java, переводы, советы бывалых

Всем привет!

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

Автор: monzdrpower

Источник

Поделиться

* - обязательные к заполнению поля