Разработчики несут большую ответственность за принятие правильного решения каждый день, и лучшее, что помогает принимать правильные решения, – это опыт. Не у всех есть большой опыт в разработке программного обеспечения, но каждый может использовать опыт других. Вот несколько советов, которые я приобрел благодаря своему опыту работы с Java, и я надеюсь, что это поможет вам улучшить читабельность и надежность вашего кода Java.
Принципы программирования
Не пишите код, который работает только . Стремитесь написать код, который может поддерживаться — не только вами, но и кем-либо еще, кто может в конечном итоге работать над программным обеспечением в какой-то момент в будущем.
80% времени разработчик читает код и 20% пишет и тестирует код. Поэтому, пожалуйста, сосредоточьтесь на написании читаемого кода!
Ваш код не должен нуждаться в комментариях, чтобы понять, что он делает!
Чтобы помочь нам разработать хороший код, существует множество принципов программирования, которые мы можем использовать в качестве руководящих принципов. Ниже я перечислю наиболее важные из них.
- ПОЦЕЛУЙ — Это расшифровывается как “Будь проще, глупый”. Вы можете заметить, что разработчики в начале своего пути пытаются реализовать сложный, неоднозначный дизайн.
- СУХОЙ — “Не Повторяйся”. Старайтесь избегать любых дубликатов, вместо этого вы помещаете их в одну часть системы или метода.
- ЯГНИ — “Тебе Это Не Понадобится”. Если вы столкнетесь с ситуацией, когда вы спрашиваете себя: “Как насчет добавления дополнительных (функций, кода и т. Д.)? “, вам, вероятно, нужно это переосмыслить.
- Чистый код поверх умного кода — Говоря о чистом коде, оставьте свое эго за дверью и забудьте о написании умного кода .
- Избегайте преждевременной оптимизации — Проблема с преждевременной оптимизацией заключается в том, что вы никогда не сможете по-настоящему узнать, где будут узкие места программы, до тех пор, пока это не произойдет.
- Единая ответственность — Каждый класс или модуль в программе должен заботиться только о предоставлении одного бита определенной функциональности.
- Композиция вместо наследования — Объекты со сложным поведением должны содержать экземпляры объектов с индивидуальным поведением, а не наследовать класс и добавлять новые модели поведения.
- Объектная гимнастика — Объектная гимнастика – это упражнения по программированию , формализованные в виде набора 9 правил
- Терпеть неудачу быстро, терпеть тяжелую неудачу — Принцип отказоустойчивости означает остановку текущей операции, как только возникает какая-либо непредвиденная ошибка. Соблюдение этого принципа обычно приводит к более стабильному решению
Пакеты
- Отдавайте предпочтение структурированию пакетов по проблемам домена , а не по техническим уровням.
- Отдавайте предпочтение макетам, которые способствуют инкапсуляции и скрытию информации для защиты от неправильного использования, а не организации классов по техническим причинам.
- Рассматривайте пакеты как предоставляющие строгий API—интерфейс – не раскрывайте внутреннюю работу (классы), которые предназначены только для внутренней обработки.
- Не общедоступная область доступа для классов, которые должны использоваться только внутри пакета.
Занятия
Статический
- Не разрешайте создание экземпляра статического класса. Всегда создавайте частный конструктор.
- Статические классы должны быть без состояния, неизменяемыми, не допускать создания подклассов и потокобезопасными.
- Статические классы должны быть свободны от побочных эффектов и предоставляться в виде утилит, таких как фильтрация списка.
Наследование
- Предпочитайте композицию наследованию.
- Не открывайте защищенные поля. Вместо этого предоставьте защищенный средство доступа.
- Если переменная класса может быть помечена как окончательная , сделайте это.
- Если наследование не ожидается, сделайте класс окончательным .
- Отметьте метод final , если только подклассам не будет разрешено его переопределять.
- Если конструктор не требуется, не создавайте конструктор по умолчанию без логики реализации. Java автоматически предоставит конструктор по умолчанию, если он не указан.
Интерфейсы
- Не используйте шаблон интерфейса констант (интерфейс констант), поскольку он позволяет классам реализовывать и загрязнять API. Вместо этого используйте статический класс. Это дает дополнительное преимущество, позволяя выполнять более сложную инициализацию объекта в статическом блоке (например, заполнение коллекции).
- Избегайте чрезмерного использования интерфейса
- Наличие одного и только одного класса, реализующего интерфейс, может привести к чрезмерному использованию интерфейсов, и это приносит больше вреда, чем пользы. Больше
- “Программа для интерфейса, а не для реализации” не означает, что вы должны сопоставлять каждый из ваших классов домена с более или менее идентичным интерфейсом, делая это, вы нарушаете ЯГНИК
- Всегда делайте интерфейсы небольшими и специфичными, чтобы клиенты могли знать только о тех методах, которые их интересуют. Проверьте интернет-провайдера из SOLID.
Финализаторы
- Объект#finalize() следует использовать разумно и только в качестве средства защиты от сбоев для очистки ресурсов (например, закрытия файла). Всегда предоставляйте явный метод очистки (например, close()).
- В иерархии наследования всегда вызывайте функцию finalize() родителя в блоке try . Очистка класса должна быть в блоке finally .
- Если явный метод очистки не был вызван и завершитель закрыл ресурсы, запишите эту ошибку в журнал.
- Если регистратор недоступен, используйте обработчик исключений потока (который в конечном итоге делегирует стандартную ошибку , которая фиксируется в журналах).
Общие
Утверждения
Утверждение, обычно в форме проверки предварительного условия, обеспечивает выполнение контракта типа быстро, без сбоев способом. Их следует использовать широко, чтобы выявлять ошибки программирования как можно ближе к источнику.
Состояние объекта:
- Объект никогда не должен создаваться или переходить в недопустимое состояние.
- Что касается конструкторов и методов, всегда описывайте и применяйте контракт с помощью проверок.
- Следует избегать ключевого слова Java assert , так как оно может быть отключено и, как правило, является хрупкой конструкцией.
- Используйте служебный класс Утверждения , чтобы избежать подробных условий if-else для проверки предварительных условий.
Дженерики
Полное, чрезвычайно подробное объяснение доступно в Java Generics FAQ . Ниже приведены общие сценарии, о которых должны знать разработчики.
- Когда это возможно, предпочитайте использовать вывод типа, а не возвращать базовый класс/интерфейс:
// MySpecialObject o = MyObjectFactory.getMyObject(); publicT getMyObject(int type) { return (T) factory.create(type); }
- Если тип не может быть выведен автоматически, встроите его.
public class MySpecialObject extends MyObject{ public MySpecialObject() { super(Collections.emptyList()); // This is ugly, as we loose type super(Collections.EMPTY_LIST(); // This is just dumb // But this is beauty super(new ArrayList ()); super(Collections. emptyList()); } }
Подстановочные знаки: Используйте расширенный подстановочный знак, когда вы получаете значения только из структуры, используйте супер подстановочный знак, когда вы помещаете значения только в структуру, и не используйте подстановочный знак, когда вы делаете и то, и другое.
Все любят ГРУДНЫЕ мышцы ! ( Производитель – расширяет, Потребитель – супер)
Использовать Foo расширяет T> для производителя T. расширяет T>
для производителя T. Использовать Foo супер T>
Одиночки
Синглтон никогда не должен быть написан в классическом стиле Шаблоны проектирования , что вполне допустимо в C++ (как это было написано), но неуместно в Java.
- При правильной потокобезопасности никогда не выполняйте следующее. (Это было узким местом в производительности!)
public final class MySingleton { private static MySingleton instance; private MySingleton() { // singleton } public static synchronized MySingleton getInstance() { if (instance == null) { instance = new MySingleton(); } return instance; } }
- Если ленивая инициализация действительно желательна, то комбинация двух подходов выполнит эту работу!
public final class MySingleton { private MySingleton() { // singleton } private static final class MySingletonHolder { static final MySingleton instance = new MySingleton(); } public static MySingleton getInstance() { return MySingletonHolder.instance; } }
- Весна: По умолчанию компонент зарегистрирован с одноэлементной областью, что означает, что контейнер создаст только один экземпляр и подключит его ко всем потребителям. Это обеспечивает ту же семантику, что и обычный синглтон, без ограничений производительности или связи.
Исключения
- Используйте проверенные исключения для восстанавливаемых условий и исключения во время выполнения для ошибок программирования. Пример: Получение целого числа из строки.
- Плохой: Исключение NumberFormatException расширяет исключение RuntimeException, поэтому оно предназначено для указания на ошибки программирования.
- Не делайте следующих действий:
// String str = input string Integer value = null; try { value = Integer.valueOf(str); } catch (NumberFormatException e) { // non-numeric string } if (value == null) { // handle bad string } else { // business logic }
- Правильное использование:
// String str = input string // Numeric string with at least one digit and optional leading negative sign if ( (str != null) && str.matches("-?\\d++") ) { Integer value = Integer.valueOf(str); // business logic } else { // handle bad string }
- Вы должны обрабатывать исключения в нужном месте, нужное место находится на уровне домена.
- НЕПРАВИЛЬНЫЙ ПУТЬ — Слой данных объектов не знает, что делать, когда возникает исключение базы данных.
class UserDAO{ public ListgetUsers(){ try{ ps = conn.prepareStatement("SELECT * from users"); rs = ps.executeQuery(); //return result }catch(Exception e){ log.error("exception") return null }finally{ //release resources } } }
- РЕКОМЕНДУЕМЫЙ СПОСОБ — Уровень данных должен просто повторно создать исключение и передать ответственность за обработку исключения или нет на нужный уровень.
class UserDAO{ public ListgetUsers(){ try{ ps = conn.prepareStatement("SELECT * from users"); rs = ps.executeQuery(); //return result }catch(Exception e){ throw new DataLayerException(e); }finally{ //release resources } } }
Исключения, как правило, должны регистрироваться НЕ в момент их возникновения, а в момент их фактической обработки. Регистрация исключений, когда они выбрасываются или повторно отбрасываются, имеет тенденцию заполнять файлы журналов шумом. Также обратите внимание, что трассировка стека исключений фиксирует, где исключение было сгенерировано в любом случае.
Одобряйте использование стандартных исключений
Используйте исключения, а не коды возврата.
Равно и хэш-код
Существует ряд проблем, о которых следует знать при написании надлежащих методов эквивалентности объектов и хэш-кода. Чтобы упростить использование, используйте java.util. Объекты’ равны и хэш .
public final class User { private final String firstName; private final String lastName; private final int age; ... public boolean equals(Object o) { if (this == o) { return true; } else if (!(o instanceof User)) { return false; } User user = (User) o; return Objects.equals(getFirstName(), user.getFirstName()) && Objects.equals(getLastName(),user.getLastName()) && Objects.equals(getAge(), user.getAge()); } public int hashCode() { return Objects.hash(getFirstName(),getLastName(),getAge()); } }
Управление ресурсами
- Методы безопасного высвобождения ресурсов:
- Инструкция try-with-resources гарантирует, что каждый ресурс будет закрыт в конце инструкции. Любой объект, реализующий java.lang. Автоматически закрываемый, который включает в себя все объекты, реализующие java.io .Закрываемый, может быть использован в качестве ресурса.
private doSomething() { try (BufferedReader br = new BufferedReader(new FileReader(path))) { try { // business logic } }
Обеспечьте Крючки Отключения
Предоставьте перехват завершения работы , который будет вызван, если JVM будет корректно завершена. (Это не будет обрабатывать внезапные отключения, например, из-за отключения электроэнергии)
Это рекомендуемая альтернатива вместо объявления метода finalize() , который будет выполняться только в том случае, если System.runFinalizersOnExit() имеет значение true (по умолчанию равно false).
public final class SomeObject { var distributedLock = new ExpiringGeneralLock ("SomeObject", "shared"); public SomeObject() { Runtime .getRuntime() .addShutdownHook(new Thread(new LockShutdown(distributedLock))); } /** Code may have acquired lock across servers */ ... /** Safely releases the distributed lock. */ private static final class LockShutdown implements Runnable { private final ExpiringGeneralLock distributedLock; public LockShutdown(ExpiringGeneralLock distributedLock) { if (distributedLock == null) { throw new IllegalArgumentException("ExpiringGeneralLock is null"); } this.distributedLock = distributedLock; } public void run() { if (isLockAlive()) { distributedLock.release(); } } /** @return True if the lock is acquired and has not expired yet. */ private boolean isLockAlive() { return distributedLock.getExpirationTimeMillis() > System.currentTimeMillis(); } } }
Разрешить ресурсам истекать (а также быть возобновляемыми) совместно используется серверами. (Это позволяет восстанавливаться после внезапного прерывания, такого как перебои в подаче электроэнергии).
Смотрите пример кода выше, в котором используется Общая блокировка с истекающим сроком действия (блокировка, которая является общей для всех систем).
Дата-Время
Java 8 представляет новый API даты и времени в пакете java.time. В Java 8 представлен новый API даты и времени, устраняющий следующие недостатки старого API даты и времени: не потокобезопасность, плохой дизайн, сложная обработка часовых поясов и т. Д.
Совпадение
Общие
- Остерегайтесь следующих библиотек, которые на удивление не являются потокобезопасными. Всегда синхронизируйте с объектами, если они совместно используются несколькими потоками.
- Дата ( не неизменяемая ) — Используйте новый потокобезопасный API даты и времени;
- SimpleDateFormat — Используйте новый потокобезопасный API даты и времени;
- Предпочитаю использовать классы java.util.concurrent.atomic , а не создавать переменные volatile .
- Поведение атомарных классов более очевидно для среднего разработчика, тогда как volatile требует понимания модели памяти Java.
- Атомарные классы обертывают переменные volatile в более удобный интерфейс.
- Поймите варианты использования, в которых volatile подходит. (см. статью )
- Используйте вызываемый, когда требуется проверяемое исключение, но нет возвращаемого типа. Поскольку экземпляр Void не может быть создан, это сообщает о намерении и может безопасно возвращать null .
Нити
- java.язык. Поток следует считать амортизированным. Хотя официально это не так, почти во всех случаях пакет java.util.concurrent обеспечивает более чистое решение проблемы.
- Считается плохой практикой расширять java.язык. Поток — вместо реализации Запускаемый и создайте новый поток с экземпляром в конструкторе (правило композиции над наследованием).
- Предпочитайте исполнителей и потоки, когда требуется параллельная обработка
- Всегда рекомендуется указать собственную фабрику пользовательских потоков для управления конфигурацией создаваемых потоков. Больше
- Используйте DaemonThreadFactory в исполнителях для некритических потоков, чтобы пул потоков можно было немедленно отключить при завершении работы сервера. больше
this.executor = Executors.newCachedThreadPool((Runnable runnable) -> { Thread thread = Executors.defaultThreadFactory().newThread(runnable); thread.setDaemon(true); return thread; });
- Синхронизация Java больше не медленная (55-110 нс). Не избегайте этого, используя сломанные трюки, такие как двойная блокировка .
- Предпочитайте синхронизацию с внутренним объектом, а не с классом, так как пользователи могут синхронизироваться с вашим классом/экземпляром.
- Всегда синхронизируйте несколько объектов в одном и том же порядке, чтобы избежать взаимоблокировок.
- Синхронизация с классом по своей сути не блокирует доступ к его внутренним объектам. Всегда используйте одни и те же блокировки при доступе к ресурсу.
- Имейте в виду, что ключевое слово synchronized не считается частью сигнатуры метода и, следовательно, не будет унаследовано.
- Избегайте чрезмерной синхронизации, это может привести к снижению производительности и взаимоблокировке. Используйте ключевое слово synchronized строго для фрагмента кода, требующего синхронизации.
Коллекции
- Используйте параллельные коллекции Java-5, когда это возможно в многопоточном коде. Они безопасны и обладают превосходной производительностью.
- Используйте CopyOnWriteArrayList вместо synchronizedList, когда это необходимо
- Используйте Collections.unmodifiablelist(…) или скопируйте коллекцию при ее получении в качестве параметра новый список массивов(список) . Избегайте изменения локальных коллекций извне вашего класса.
- Всегда возвращайте копию своей коллекции, избегая изменения списка извне новый список массивов(список)
- Каждая коллекция должна быть заключена в свой собственный класс, так что теперь поведение, связанное с коллекцией, имеет дом (например, методы фильтрации, применение правила к каждому элементу).
Разнообразный
- Предпочитаете лямбды анонимным классам
- Предпочитаю ссылки на методы для лямбд
- Используйте перечисления вместо констант int.
- Избегайте использования float и double, если требуются точные ответы, вместо этого используйте BigDecimal, например, деньги
- Предпочитайте примитивные типы упакованным примитивам
- Следует избегать использования магических чисел в коде. Используйте константы
- Не возвращайте значение Null. Общайтесь с вашим клиентом по методу с помощью
Необязательно
. То же самое для коллекций — Возвращайте пустые массивы или коллекции, а не нули - Избегайте создания ненужных объектов, повторного использования объектов и избегайте ненужной очистки GC
Отложенная инициализация
Отложенная инициализация – это оптимизация производительности. Он используется, когда данные по какой-либо причине считаются “дорогими”. В Java 8 мы должны использовать для этого функциональный интерфейс поставщика.
== Thread safe Lazy initialization === public final class Lazy{ private volatile T value; public T getOrCompute(Supplier supplier) { final T result = value; // Just one volatile read return result == null ? maybeCompute(supplier) : result; } private synchronized T maybeCompute(Supplier supplier) { if (value == null) { value = supplier.get(); } return value; } } Lazy lazyToString= new Lazy<>() return lazyToString.getOrCompute( () -> "(" + x + ", " + y + ")");
На этом пока все, надеюсь, это было полезно!
Бесплатный Продвинутый курс Java
Я являюсь автором курса Продвинутая Java для взрослых . Этот курс содержит продвинутые, а не обычные уроки. На этом курсе вы научитесь мыслить иначе, чем те, кто имеет ограниченное представление о разработке программного обеспечения. Я спровоцирую вас задуматься о решениях, которые вы принимаете в своей повседневной работе, которые могут быть не самыми лучшими. Этот курс предназначен для разработчиков среднего и старшего звена, и мы будем учить не особенностям языка Java, а тому, как руководить сложными проектами Java.
Лекции этого курса основаны на торговой системе, проекте с открытым исходным кодом, размещенном на моем Github .
Оригинал: “https://dev.to/apssouza22/java-best-practices-quick-reference-55hh”