Метод должен(!) выполнять 1 единственную задачу, которое можно понять из наименования метода. Маркером для выделения нового метода может быть то, что новый метод может иметь отличное название
Должна читаться последовательность вызова методов - они должны быть как набор инструкций, выстроенных сверху-вниз, на конвейере (или как TODO)
Длинные if-else и switch следует преобразовывать в фабрику, создающие соответствующие реализации:
1 2 3 4 5 6
switch (x.type) { case TYPE_1: return createCalc1(); case TYPE_2: return createCalc2(); case TYPE_3: return createCalc3(); default: return defaultCalc(); }
Лучше переделать в:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
interfaceCalc { doublecalc(); }
classCalcFactory { publicstatic Calc buildCalc(CalcType type) { switch(type) { case TYPE_1: returnnewCalcType1(); case TYPE_2: returnnewCalcType2(); case TYPE_3: returnnewCalcType3(); default: returnnewRandomCalc(); } } }
Так же, крайне рекомендуется в случае примера выше (и подобных) использовать enum. Особенно, при ограничениях типизации средствами sealed классов. 6. Длинные имена методов (но информативные) - это нормально. Публичным (public) методам все же лучше давать короткие и читабельные. 7. У методов желательно наличие не более 3 параметров. Если требуется больше - лучше объединить их в неизменный (immutable) класс (Record предпочтительнее) с соответствующей реализацией. 8. Изменяемый (mutable) параметр (тот который может поменяться внутри метода) лучше в метод не передавать. В случае если метод все же как-то меняет его, то следует возвращать его в return - это хотя бы будет маркером, сигнализирующем что с объектом может что-то произойти внутри метода! 9. boolean в качестве параметра - плохая практика! Лучше разбить на 2 метода. Особенно плохо это для констукторов - в этом случае лучше использовать фабрики. 10. Иногда в имена методов можно добавить наименования параметров, например:
1
deleteUser(User user, User admin) -> deleteUserByAdmin(user, admin)
Так же хорошей практикой для примера выше будет выделение интерфейса, или наследование:
1
deleteUser(User user, Admin user)
Таким образом намного проще понять сигнатуру метода. Особенно эффективно выполнять такую модификацию в случае если у метода несколько параметров с одинаковым типом. 11. Результаты выполнения действий метода лучше хранить внутри объекта. Например, изменение баланса счета или изменение состояния датчика лучше изменять и хранить в самом объекте (а не в сервисе, который выполняет проксирующую функцию). 12. Наименование метод должен информировать что метод либо что-то делает, либо указывать на то что метод отвечает на какой-то вопрос, например:
1
booleanset(X x);
Из сигнатуры не понятно что данный метод делает, что он возвращает и для чего? Так, например, можно изменить:
1 2
booleancanSet(X x); voidset(X x)
Тогда вызов:
1
if(canSet(x)) set(x);
Впрочем, подобные случаи именования встречаются и в стандартных Java библиотеках.
Вместо бесконечных if-else бывает лучше использовать выкидывание исключений - это позволит сильно сократить код (правда, небольшой ценой CPU и RAM)
Блоки try-catch сильно загромождают код. Если исключение можно изменить, то рекомендуется наследовать его от RuntimeException. Если же модификация исключения не доступна, то лучше добавить private метод и внутри него обернуть метод с потенциальным исключением в try-catch.
Зачастую лямбда-выражение сложно читать, если метод, который в нем вызывается, выкидывает checked-исключение - это влечет за собой необходимость добавления блока try-catch в рамках лямбда-выражения.
Для повышения читабельности кода, можно обернуть вызываемый метод в прокси метод, который перехватит checked исключение и выкинет RuntimeException. Часто подобные методы имеют такое же название, что и оригинал, но с постфиксом -Safe:
Вместо static методов лучше использовать полиморфизм. Это не касается Utility, Helper, фабрик и подобных классов
Классы
Последовательность описания класса желательно соблюдать следующей:
public static переменные
private static переменные
static блоки
public -> protected -> privateinstance переменные
instance блоки
Конструкторы
public -> protected -> private методы в соответствии с очередностью вызова
Метод лучше помечать как private. Если на метод необходимо наложить UnitTest, то достаточно изменить модификатор доступа на protected (целевой класс и тестовый класс должны располагаться в одном пакете)
Желательно проектировать классы максимально минималистичными
Класс должен отвечать 1 задаче - согласно SOLID, Solid Responsibility Principle
В классе желательно сократить количество instance переменных. Чем активнее они будут использоваться - тем лучше
Класс должен быть открыт к расширениям, но закрыт к изменениям - согласно SOLID, Open-Closed Principle
Класс должен зависеть от абстракций, а не от реализаций - согласно SOLID, Depencency Inversion Principle
Полиморфизм вместо if-else
Весь хардкод желательно выносить в static переменные. В идеале - в настраиваемые параметры приложения, которые будут иметь значения по умолчанию, но можно будет изменить при старте
Объекты
Выносить абстракции в интерфейсы. В интерфесах открывать только полезные методы, которые реально будут востребованы
Согласно закон Деметры, следует соблюдать слабую связанность между сущностями - один класс общается только со знакомыми ему типами (классами), а не выполняет многоуровневые обращения к связанным объектам:
Так же, если результат используется где-то в другом методе или классе, то, может, лучше сразу передать управление ему? 3. DTO. Объекты с переменными, но без методов. Из методов могу быть только setter/getter и конструкторы 4. Active Record - это DTO, расширенные навигационными методами, типа save, find, delete, … В них не хранится бизнес-логика. Методы лишь помощники в транспорте объектов (данных). Логику работы объектов лучше хранить на уровне сервисов. 5. Конструкторы максимально облегчить! В них не должны выполняться сложные вычисления и, тем более, обращения к внешним ресурсам. В конструкторы должны заходить только данные, а их подготовку можно вынести, например, в фабрику. Таким образом:
проще писать Unit-тесты
проще выполнять CDI инъекции
выделение памяти не страдает
Ошибки
Все нестандартные поведения лучше оборачивать в исключения
Избегать статусов в виде кодов, строк и т.п. Если работа метода отработала корректно - никакой ошибки; иначе выкидывать Exception
checked исключения больше являются рудиментом Java. Лучше наследовать исключения от RuntimeException. Особенно это актуально для private и protected методов
Исключения должны содержать подробную информацию о месте ошибки, причине, деталях, окружении и т.п. При этом, никаких ссылок на сложные объекты или user-типы, лучше не делать
Лучше выстраивать иерархию ошибок (наследованием) - от более обобщенных, к более детализированным. Таким образом можно будет обрабатывать их группами
При работе с внешними библиотеками удобно оборачивать внешние ошибки в собственные - это даст возможность с легкостью переходить с 1 библиотеки на другую
Обработку ошибок выгодно перекладывать на уровень ниже, так чтобы абстракция сама могла решать что делать с ошибкой (например, выполнять какое-то действие по умолчанию).
Таким образом клиент метода всегда пользуется данными, а не обрабатывает ошибки. Можно обернуть в Optional, если какого-то действия по умолчанию быть не может. Данный паттерн именуется Special Case Pattern
Метод не должен возвращать null - это приводит к NullPointerException, или дополнительным проверкам на null
Еще про именование
Названия не должны включать аббревиатуры внешних сервисов, ресурсов, функциональных участков и т.п. Всегда есть более уместная читабельная форма записи (пусть даже она будет несколько более длинной) - смысл имени всегда должен быть понятен интуитивно
Если в методе есть return, то необходимо (интуитивно) понимать что там может находиться и как это использовать
В именах переменных или методов слова типа List, Map, Set, Array, Number, String и т.д. лучше применять только в тех случаях когда тип данных соответствует данной части наименования
В именах переменных не следует писать *Var/*Variable - итак понятно что это переменная. Это же касается и методов (*Method/*Func) и классов (*Class/*Cl) и интерфейсов (*Interface/*Iface/*I). Это же применимо и к тестам - зачем именовать каждый test метод *Test?
*Info, *Data не нужно писать в названиях классов. Лучше добавить пакет
Все имена должны быть читабельными и произносимыми на английском
Поиск (даже стандартными инструментами) по “приблизительному” имени должен давать результат. Необходимо максимально облегчить поиск и навигацию
Никаких префиксов типа vUser, IUser, UserImpl, _username и т.д.
i, j, k, l, m, n, … должны использоваться только в итераторах. Причем, только в числовых. Глубина блоков с итерациями так же имеют некоторые оговорки
Имена классов - существительные, а методов - глаголы (исключения возможны, например для Builder Pattern)
Часто создание объектов удобно выносить в static метод, особенно если имплементация зависит от параметров. Можно так же использовать Builder Pattern, но, в основном, для больших классов
Для однотипных методов лучше заводить однотипные наименования для всего приложения. В противном случае, могут возникать вопросы, например:
В чем между ними разница? Это же правило касается и классов.
Часто использование технических или околотехнических терминов поможет в понимании назначения метода или переменной, например: ReportQueue, UserRoleMatrix. Конечно, если имена имеют подобную особенность, они должны оправдывать ожидания
Переменные можно объединять в группы просто именованием: userName, userCity, userPhone, userCaption, userAddress, companyCaption, companyRegistrationDate, companyAddress и т.д. Если группа очень большая следует завести отдельный класс
Если очевидно что классы группируются по какому-то признаку лучше вывести их в отдельный пакет
Если имена в разных областых видимости (scope’ах) совпадают у переменных, то для них лучше завести новые имена, описывающие их уникальным образом. Иначе полиморфизм может лишь усложнить жизнь
Комменты
Много комментов нужно только плохому коду
Вместо комментов лучше нормально и информативно именовать методы. А так же следать декомпозицию больших методов
TODO в комментах всегда полезны!
Если действие в коде неочевидно, то лучше оставить комментарий - почему код именной такой
Для публичных библиотек надо оставлять JavaDocs. Хотя бы для public методов
Коммент следует оставлять не “для себя”, а как способ донести неочевидность кода до другого разработчика (если кодом это сделать невозможно)
Комменты должны быть по месту и точечно! Их переизбыток мешает!
Удаление кода не следует комментировать! Для этого есть VCS (Git, SVN, Mercurial и т.п.)
Комментировать следует только рядом стоящий код. Не следует комментировать что будет через 10 строк, или то что было в предыдущем методе на 7 строк выше
Комментарий короткий и четкий
Если после прочтения комментария возник вопрос (или даже несколько) - это плохой комментарий!
Комментарии быстро устаревают и их редко меняют. По-этому лучше их избегать или, хотя бы, вовремя удалять
Комментарии должны содержать только техническую информацию по реализации, никакой бизнес-логики
Перед комментарием следует дважды подумать - а стоит ли оно того?!
Форматирование
Рекомендуемая длинна файла - до 200 строк. Рекомендуемая длинна строки - 120 символов
В верхней части файла должна содержаться макс. Полезная информация, раскрывающая задачи файла. Если это *.java файл, то наименование файла (а следовательно и главного класса) должно описывать задачи всего файла. Посредством пакетов можно успешно группировать классы и модули. Чем больше детализации по задаче (файла), том ниже должен быть расположен блок (вложенный класс или метод)
Код надо рассматривать как газету - в заголовке максимально важная информация, описывающая тематику (назначение). Важные блоки лучше разбивать пробелами
Определение переменной максимально близко к первому использованию. Так, если методы будут маленькие, то и переменные будут в верхней их части
Вызываемые методы должны быть максимально близко к месту их вызова. В действительности, желательно сразу после их первого вызова
Позитивные if-else читать значительно проще
“Мертвые” методы и код следует удалять. Для истории есть VCS
В *.java файле только Java код! Все остальное выносить во внешние ресурсы
Избегать дублирования. Даже неочевидного, типа: Optional::isPresent / Optional::isEmpty