Skip to content

Latest commit

 

History

History
2086 lines (1377 loc) · 169 KB

conventions russian.adoc

File metadata and controls

2086 lines (1377 loc) · 169 KB

conventions & programming culture

  • Camel case: numberOfPeople A series of words, with each intermeidiate word started with a capital letter. Called "Camel Case" because the capital letters make it look like the humps of a camel.

  • Kebab case: number-of-people Hypehated words - like chunks of meat or vegatables on a kebab skewer. Note that cannot case only works in a gew languahes such as Tcl and Perl 6, as the minus sign most usually is the subtraction operator.

  • Snake case: number_of_people Words separated with underscores - the word snakes along past the underscores. Unlike Kebab case which is of limited (language) use, you can use Snake Case with most modern languages.

  • Hungarian (Systems) notation: iNumberOfPeople In hungarian notation, you preceed the variable name with an additional character or characters to indicate the type of variable - in Hungarian Systems notation, that’s an indiction of the internal data type such as i for Integer. You can see the developemnt of Hungarin Systems Notation from old Fortran conventions where variable names starting with I J K L M or N were integers, and others were floats.

  • Hungarian (Apps)) notation: cntNumberOfPeople Prefixing the name of the variable with character(s) to indicate the use make of a variable within the application - in my example, I have used cnt to incicate it’s a counter …​ and in my old Fortran programs you’ll find "JPxxxxx" all over th eplace to indicate an integer pointer.

  • Shortening and English / American Should you use init, initialise or initialize as the name for a piece of setup code? Some languages will force a built in standard on you for such specials, but I strongly recommend that you use your own convention (for your organisation) rather than mixing them up. There’s nothing quite so frustrating as typing to debug a piece of code where sometimes the word colour is used, and at other times the word is color

1) аннотации

NAMING
  • добавить к имени суффикс Annotation

CONVENTIONS
  • следует указать области применения

            @Target( { ElementType.ANNOTATION_TYPE,
            ElementType .CONSTRUCTOR,
            ElementType .FIELD,
            ElementType .LOCAL_VARIABLE,
            ElementType .METHOD,
            ElementType .PACKAGE,
            ElementType .PARAMETER,
            ElementType .TYPE,
            ElementType .TYPE_PARAMETER,
            ElementType .TYPE_USE})
  • нельзя создавать пустые маркерные интерфейсы без членов, вместо них нужно использовать маркерные аннотации

  • когда лучше использовать интерфейс

    • тк интерфейс это тип, то можно задавать параметры типа Serializable и это позволяет заметить ошибки во время компиляции

    • задайте себе вопрос, могу ли я захотеть написать один или более методов, которыепринимали бы только объекты такого интерфейса?

    • хочу ли я ограничить использование этого маркера элементами определенного интерфейса навсегда?

  • когда лучше использовать аннотацию

    • если я хочу добавлять дополнительную информацию к маркеру в будущем?

2) параметры методов и проверки аргументов метода

NAMING
  • имена параметров в методах начинаются с маленькой буквы, верблюжий стиль

  • в двубуквенных акронимах обе первых буквы маленькие

            - например ioStream
  • тк параметры-методов отображаются в документации и автоподстановках то для них тоже должны действовать правила именования

  • названия параметров должны быть настолько описательными, чтобы используя с их типами, можно было определить их значение в большинстве сценариев

  • если нет иного смысла то в крайнем случае называть параметры:

            - left, right
            - value
  • НЕ использовать числовые индексы

            - например: param1, param2
  • (вариант 1) рекомендуется основывать на смысловом значении а не на типе

    • тк IDE само покажет тип

  • (вариант 2) название параметра может совпадать с названием пользовательского типа

    • это экономит значимые имена

    • но вероятно, что это название не является достаточно описательным, чтобы быть полезным

  • (??вариант3) если слово совпадает с ключевым то его искаверкать, но чтобы оно было похоже на первоначальное

    • например не class а clazz

  • (??вариант4) имя незначимого параметра, те тот который мы не используем можно писать просто прочерком

    • например (_)→"rez";

CONVENTIONS
  • лигбез терминологии:

    • параметрами метода - называются названия переменных в объявлении метода, например func(String a,int b)

    • аргументами метода - называются действительные значения передаваемые на вход методу во время выполнения, например func("mike",12)

  • reference type - это тип переменной при объявлении ** известен compile time

    • используется для overload

  • object type - реальный тип присваиваемого объекта

    • вычисляется runtime

    • используется для override

  • еще один термин для локальных переменных:

    • автоматические переменные

    • переменные в стеке

    • переменные метода

      1) тип параметра - наивысший возможный тип в иерархии
  • тогда расширится набор возможных подтипов объектов, которые может принять данный параметр

  • теоретически самый предопочтительный интерфейс Iterable<> а не конкретный тип коллекции (в случае если нужно просто перечислить элементы)

  • практически же, коллекция сейчас является предпочтительным методом для возврата значений тк и является iterable и содержит stream, из недостатков: количество элементов должно быть меньше 2^32

    • выбирать интерфейсы List, Set, Queue, Map наиболее подходящие для решаемой задачи, а не конкретные реализации

    • но для написания фреймворков рекомендуется суперобщий тип Iterable

      2) если требуется вызывать определенные методы объекта параметра то нужно наоборот ограничить "наивысший возможный тип в иерархии"
  • нельзя резервировать параметры "на будущее"

  • лучше в будущем сделать перегруженную версию метода с новым параметром

  • количество параметров должно быть не более 4-х

    • иначе юзер их не запомнит и постоянно будет все вермя смотреть в help

  • чтобы сократить количество параметров

    • разбить метод на несколько (как например в NEW-SET-CALL для каждого параметра отдельный сетер)

    • вынести группу параметров в value-класс (nested static) (helper class)

    • все параметры зашить в паттерн билдер, где параметры можно присваивать по-очереди (так что билдер применим и на мутабельном объекте)

    • использовать Currying, и присвоить эти параметры заранее функции которая вернет другую функцию (аналог нашего метода)

  • избегайте рядом параметров одного типа, чтобы юзер их не перепутал

    1. ПАТТЕРН NEW-SET-CALL В МУТАБЕЛЬНЫХ КЛАССАХ

      1) методы не имеют параметров, которые должны быть постоянными во всех вызовах метода в основных сценариях
  • тк такие постоянные аргументы должны задаваться свойствами

  • те только такие параметры которые нужны для каждого вызова и не остаются постоянными

    2) следует добавить параметр к методу если я хочу сделать акцент на параметре
  • те сделать очевидной связь между параметром и методом

  • например timeout задавать метода, несмотря на то что он одинаковый при каждом вызове

    3) следует проверить консистентность объекта, так как могут быть проблемы из за
  • в методе следует сделать проверку всех используемых свойств на null и допустимые значения

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

  • конструктор по умолчанию позволяет пользователям создать недо-инициализированный экземпляр

    • при неконсистентности объекта следует вызывать исключения IllegalStateException ::

  • сообщение исключения должно ясно объяснять, какие свойства должны быть изменены, чтобы получить объект в допустимом состояний

  • исключения должны быть вызваны тогда когда операция выполняется а не когда компонент инициализируется

ПРОВЕРКА АРГУМЕНТОВ
1) следует разрешить передачу null указателя в качестве аргумента метода и внутри метода сделать проверку на null и допустимые значения
  • чтобы избежать проверку на null до вызова функции и перенести ее внутрь вызова функции и в случае нуля внутри метода проставить значение про умолчанию и возможно, логировать такой входной аргумент

2) а если null не разрешен то вызывать ArgumentNullException
  • необходимо обязательно проверять все аргументы public, protected методов, переданные на вход методу юзером

  • необходимо осуществлять проверки аргументов столь тщательно насколько это возможно

    • тк это обеспечит лучшее сообщение об ошибке

  • при ошибке в аргументе следует вызывать ArgumentException

  • необходимо помнить что мутабельные объекты могут измениться уже после проверки

    • 1) если я не хочу распространять побочный эффект, то я могу сделать КОПИЮ объекта(!не копию ссылки), проверить ЕЕ и передать ЕЕ дальше

    • 2) я могу преобразовать/скопировать мутабельный объект в немутабельный, тогда не придется потом еще раз делать копирование на геттере

    • 3) лучше принимать только немутабельные объекты (и всю иерархию композиции класса сделать немутабельной)

    • проверку надо делать уже на копии чтобы защититься от атаки TOCTOU (time-of-check/time-of-use)

    • это применимо не только к немутабельным классам но и к мутабельным: если объект мутабельный то подумайте: Терпимы ли вы к неожиданному мутировнию объекта? Если вы неготовы к неожиданному мутированию мутабельного объекта то сделайте защитную копию ! Это применимо и к гетерам и к сетерам

1) сама проверка не обязательно должна производиться непосредственно в этом паблик методе, она может производится и на более низком уровне в подметодах и тп
2) проверки аргументов лучше поместить вверх по иерархии ближе к общему api и дальше от расширенных низкоуровневых сценариев
  • тк низкоуровневые функции вызываются чаще то и стоят они дороже

1) use assertions to check method parameters of private methods
  • use assertions for situations that can never occur

COMMON TYPES vs SPECIAL/USER TYPES
  • рекомендуется использовать специфические типы данных (например URL), а не общие (например String)

    • например, рекомендуется URL даже если вместо него можно использовать String

  • следует везде использовать специфические типы для:

    1. свойства класса

    2. возвращаемые значения методов

    3. типы параметров и реальные аргументы методов

  • для своих методов принимающих на вход СПЕЦИФИЧЕСКИЙ ТИП(например url)
    для удобства рекомендуется обеспечить для глупых юзеров перегруженные версии методов, принимающих ОБЩИЙ ТИП(например строку)
    но вызывающих версию с специфическим типом (те url)

    • если достаточно часто от пользователя принимается строка то вы должны добавить удобную перегрузку для приема строки

    • такие методы предназначаются в качестве помошников для часто встречающихся сценариев

    • будте избирательны и предоставляйте такие помошники только для часто используемых вариантов

    • нельзя все методы с параметром типа url слепо автоматически перегружать версиями со строкми тк предпочтительней api на основе url

    • нельзя чтобы была только версия метода со String без метода с URL

генерик

NAMING
  • следует называть T для очевидных параметров

  • имя параметра это имя ограничения в extends с префиксом T

    • например <TString extends String> , <TKey, TValue>

  • в параметризованном методе называть параметр E

    • чтобы отличить его от параметров генерик классов

  • альтернативная однобуквенная система именования:

    	E for an element
    	K for a map key
    	V for a map value
    	N for a number
    	T for a generic data type
    	S, U, V, and so forth for multiple generic types
CONVENTIONS
  • не рекомендуются типы <?>

    • например Collection<?>

  • не рекомендуется <T> void Method (T p1)

vararg

NAMING
  • название аргумента должно быть во множественном числе

CONVENTIONS
(1) если юзеры передают массивы с небольшим количеством элементов то их лучше оформлять vararg
  • а если юзеры будут передавать много элементов то vararg тоже подойдет

(2) следует заменить более 3-х однотипных параметра на vararg
  • например (object1,object2,object3,object4) на (…​ objects)

  • все параметры имеют один и тот же тип

  • если параметры отличаются только числовым суффиксом

  • если у метода еще нет перегруженной версии принимающей на вход массив

(3) метод с vararg, наоборот можно перегрузить методами с разными количествами параметров
  • для того чтобы обеспечить более высокую скорость

  • например добавить к методу of(E…​ elements)

                                        of(E e1)
                                        of(E e1, E e2)
                                        ...
                                        of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)
  • если юзеры почти всегда на вход передают массив то vararg не рекомендуется

  • следует всегда проверять vararg аргумент на null и если null то вызывать эксепшн

  • если вы хотите чтобы vararg имел обязательно аргументы, то вместо проверки на неноль внутри лучше объявить с отдельным первым аргументом, например

    • int min(int firstArg, int…​ remainingArgs)

РАСШИРЯЕМОСТЬ
  • рекомендуется параметры типа массив переносить в конец, чтобы оставлять возможность в будущем сделать из низ vararg

  • рекомендуется не перегружать методы с vararg тк это запутает юзера

  • методы с vararg могут прегружаться, методом уже без того-же vararg

             например, method(String param1,String ...objects)
             method(String param1,String[]  objects, String param2)

3) конструкторы

CONVENTIONS
  • для операция преобразования типов лучше использовать явный конструктор или фабрику вместо методов трансформации

    • тк нужно показать что в этом случае явно создается объект

    • например, если необъект(элементарный тип) преобразуется в объект

    • в таких операциях можно вызывать ClassCastException

  • если нет конструкторов, дефолтный конструктор нужно задавать явно

    • чтобы не сломался потом существующий код при введении конструктора с параметром

    • тк он автоматически удалит неявный дефолтный конструктор

  • конструкторы ничего не должны делать кроме приема параметров конструктора и установки свойств

    • конструкторы должны вызывать исключения если они требуются

  • нельзя вызывать переопределяемые(обычные или abstract) методы внутри конструктора

    • тк метод может быть переопределен подклассом и при вызове этого метода у еще неинициализированного подкласса из базового класса будет ошибка

    • можно вызывать super.someMethod() тк будет вызываться непереопределяемая версия

    • можно вызвать final методы и private методы тк они не переопределяемые

    • см паттерн приватных helper методов вызывающися из переопределяемых методов

  • следует предпочитать конструкторы фабрикам

    • конструктор это самый естественный способ создания объектов: в результате работы конструктора: будет создан, инициализирован и возвращен уникальный экземпляр класса определенного типа

    • юзеры будут искать их прежде чем найдут фабричные методы

    • для фабрик не работает автоподстановка в IDE

    • фабрики жертвуют возможностью обнаружения, удобством и простотой использования

    • большинство юзеров находят что шаблон конструктора более простой и лучше поддается обнаружению, потому что юзеры привыкли к объектам созданным конструкторами

I) ПАТТЕРН NEW-SET-CALL В МУТАБЕЛЬНЫХ КЛАССАХ
  • должен быть дефолтный конструктор по-умолчанию

    • рекомендуется проектировать компоненты так чтобы они могли использоваться после очень простой инициализации

    • конструкторы которые не принимают параметры называют конструкторами по умолчанию или дефолтными конструкторами

  • может быть очень простой конструктор с одним параметром (параметрами основных свойств)

    • нужно использовать параметры конструктора для установки основных свойств

    • не должно быть сложных конструкторов тк большинство параметров инициализируются через выставление свойств

    • все параметры конструктора соответствуют свойствам и просто инициализируют их

  • всегда должна быть возможность выбора:

    1. вызвать конструктор с параметрами, поэтому следует сделать конструктор с параметрами задающими все свойства

    2. вызвать дефолтный конструктор но затем установить свойства через сетеры

  • названия параметров конструктора должны совпадать с названиями свойств

    • кроме регистра первой буквы

  • простые задачи одного основного сценария должны быть выполнимы с помощью только одного объекта

    • нельзя чтобы юзеры создавали экземпляры нескольких объектов в одном сценарии

    • у каждого из 5 лучших основных сценариев, должно быть не больше одного оператора new

      • примущества

    • в том что можно устанавливать параметры выборочно

    • может быть несколько параметров varargs, например у каждого свойства свой vararg,

      • в отличие от конструктора, где в одном конструкторе может быть только один vararg

  • возможные проблемы: конструктор по умолчанию позволяет пользователям создать недо-инициализированный экземпляр

    • что может привести к недопустимому состоянию объекта при вызове метода

    • проблемы должны быть смягчены исключениями/сообщениями об ошибках в методах

    • исключения должны быть вызваны тогда когда операция выполняется а не когда компонент инициализируется

    • те в конструкторе я могу позволить присвоить null, но сигнализирую об этом в log

II) ПАТТЕРН NEW-CALL В неМУТАБЕЛЬНЫХ КЛАССАХ
  • следует проектировать типы так чтобы объекты не могли существовать в недопустимом состоянии

    • поэтому следует проверять консистентность объекта в конструкторе и вызывать IllegalStateException

    • в конструкторе делаю проверки на null и допустимые значения

  • в конструкторе сразу задаются все параметры требуемые для настройки объекта

    • настройки не могут быть изменены после инициализации в конструкторе

  • следует конструктор без параметров сделать private, чтобы уберечь юзера от ошибки

    • а для создания пустых объектов сделать явную отдельную фабрику

III) ПАТТЕРН BUILDER В неМУТАБЕЛЬНЫХ КЛАССАХ
  • клиент вызывает статический метод для получения билдера (или сразу конструктор билдера который является inner static классом). Затем клиент вызывает сеттеры на этом объекте для установки всех интересующих параметров. Наконец, клиент вызывает метод build для генерации объекта, который будет являться неизменным. Метод build вызывает private конструктор объекта принимающий объект билдера

    • можно получить немутабельный объект в консистентном состоянии, как бы вручную «замораживая» объект после того, как его создание завершено,

    • один билдер может использоваться для создания нескольких объектов

    • позволяет избавиться от каскада/лестницы/телескопических конструкторов

    • хуже производительность тк надо создавать помимо объекта еще и builder

    • если помимо билдера уже много конструкторов то сложно поддерживать такой код

IV) CURRYING
  • все параметры, которые должный быть постоянными вынести в отдельную функцию, которая должна возвращать другую функцию(= нашему первоначальному методу)

    • а переменные параметры будут у этой второй возвращаемой функции

ПРЕИМУЩЕСТВА ФАБРИКИ
1) singleton
  • метод возвращающий синглетон должен называться getInstance

  • НО самый лучший вариант реализации синглетона - через перечисление

2) builder/сборка объекта
  • обычно нужен для немутабельных классов

3) кеширование
4) совместное использование объектов, object pool
  • для сохранения еденичности экземпляров немутабельных классов (как в strings constant pool)

5) когда конкретный подтип возвращаемого типа будет известен только рантайм
  • недостаток конструкторов, то что вы не можете динамически, во время выполнения разрешить тип того, что возвращаете, и при этом не можете возвратить ранее распределенный экземпляр класса(например закешированный).

  • Если я уверен что никогда не буду нуждаться в этих возможностях, то конструкторы это лучший выбор

  • создает только экземпляры конкретного типа/подтипа (а не вообще все в разнобой)

  • может возвращать объект скрытого private класса / сокрытие реализации классов

  • на момент написания фабрики , типа возвращаемого класса может еще не быть / позднее связывание в службах и сервисах с поставщиками услуг

6) рекомендуется использовать фабрики, если наличие поименованного метода является единственным способом сделать операцию очевидной
  • тк у конструкторов не может быть названий то иногда при использовании конструктора ощущается недостаток в информации о контексте и вследствие этого юзер плохо себе представляет что делает операция

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

7) для операций конвертации Value типов
  • такие методы принимают тип значения в одном представлении и преобразуют его в экземпляр класса другого типа значений, сохраняя то же самое логическое состояние

  • это делается не через конструктор чтобы создание объекта не было заметно, было неявным

  • например, d=DateTime.parse('01/02/2019')

  • например, e=MyEnum.valueOf("SUMMER")

8) копирующие фабрики:
  • для немутабельных классов нет смысла поддерживать копирование объектов (в конструкторах или фабриках), поскольку копии будут фактически неотличимы от оригинала.

  • для немутабельных классов статические методы генерации копии имеют смысл, если получают на вход параметр более общего интерфейса и мутабельный аргумент (см как в коллекциях Java) они называются conversion constructors / conversion factories

9) копирующие фабрики: в мутабельных объектах серийные номеры и идентификаторы копировать ненадо
  • тк они естественно должны различаться

8) фабрика может вывести генерик типы типы из контекста (type inference) тем самым сделав запись компактней
9) можно помещать такие фабрики в интерфейсы (и скрытый класс прямо в метод интерфейса)
10) если использую только статик фабрику (из за всех private constructor) то такой класс нельзя отнаследовать
11) проблема обнаружения таких фабричных методов решается соглашениями об именовании
12) могу использовать фабрику в качестве аргумента лямбды поставщика

* например, Elvis::instance как Supplier<Elvis>

ПАТТЕРН ФАБРИЧНЫЙ МЕТОД
  • часто это статический метод

  • часто фабричный метод принадлежит этому же типу, который он и производит

  • следует называть метод of, CreateCopyOf, CreateDeepCopy

    		- valueOf (аналог from или of) вернет объектный тип. Это статический метод генерации . Возвращает экземпляр, который, грубо говоря, имеет то же значение, что и его параметры. Статические методы генерации с таким названием фактически являются операторами преобразования типов.
    		- of  аггрегирующий метод, берет несколько параметров и инкорпорирует их
    			- в java9 методы возвращающие немутабельные копии коллекций называются "of" (берут входные параметры не конвертируя их).
    			- более краткая альтернатива для valueOf
    			- пример, List.of("1","2","3")
    	     - instance/getInstanceвозвращает экземпляр, который описан параметрами, однако говорить о том, что он будет иметь то же значение, нельзя. В случае с синглтоном этот метод возвращает единственный экземпляр данного класса. Это название является общепринятым в системах с предоставлением услуг
    		- пример, StackWalker luke = StackWalker.getInstance(options)
    	     - create/newInstance то же, что и getInstance, только  newInstance  дает гарантию, что каждый экземпляр отличается от всех остальных.
    	     - getType то же, что и getInstance, но используется, когда метод генерации находится в другом классе. Туре обозначает тип объекта, возвращенного методом генерации.
    	      - newTypeто же, что и newInstance, но используется, когда метод генерации находится в другом классе. Туре обозначает тип объекта, возвращенного методом генерации.
    	     - type  (короткий аналог getType / newType)
    		- пример, List<Some> l = Collections.list(enum)
    	  	- from конвертирует входные параметры в инстанс целевого класса (может включать потерю информации)
    				- например Date d=Date.from(instant)
    	 	- parse парсит входную строку для того чтобы произвести инстанс целевого класса
    	  	- parseInt  вернет примитивный тип
ПАТТЕРН ФАБРИЧНЫЙ ТИП / АБСТРАКТНАЯ ФАБРИКА
  • это тип с абстрактным фабричным методом

  • фабричный метод возвращает динамически определяемый подкласс

  • следует называть тип имяСоздаваемогоТипа+Factory

CONVENTIONS
  • пользователь не имеет никакого контроля над тем когда вызовется инициализатор

  • нельзя вызывать исключения из инициализатора

    • тк тогда тип будет непригоден для использования

    • но этот тип все равно можно окружить try-catch

  • нерекомендуется использовать инициализатор для инициализации статических полей

    • лучше инициализировать статические поля при объявлении например потому что их можно сделать final

    • лучше инициализировать статические поля в отдельном статик методе, который может вернуть ожидаемый эксепшен

4) библиотеки

NAMING
  • название библиотеки может совпадать с иерархией пакетов

    • company-product|technology-feature.DLL

    • company-product|technology.DLL

    • каждое слово с маленькой буквы

СЦЕНАРИИ
  • разбить модуль на пакеты основных сценариев и расширенных

    • высокоуровневое api и низкоуровневое

    • высокоуровневое api обертывает низкоуровневое api в удобные фасады

  • НЕ рекомендуется создавать глубокие иерархии пакетов

    • тк их трудно просматривать все время подимаясь вверх по иерархии

  • НЕ рекомендуется создавать большое количество пакетов

  • рекомендуется моделировать понятия высокого уровня (например физические объекты) а НЕ задачи системного уровня

    • например компоненты должны моделировать файлы, каталоги, диски а не потоки, форматтеры, компараторы

1) ОСНОВНЫЕ СЦЕНАРИИ
  • большинство разработчиков использует малый набор общих сценариев

    • поэтому нужно сосредоточить все усилия на этих нескольких общих сценариях

  • те основные сценарии это часто используемые области функциональных возможностей

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

  • пакеты основных сценариев должны быть выше в иерархии чем расширенных

    • те типы расширенных сценариев должны быть помещены в ПОДпакеты/ПОДклассы основных сценариев

  • самые простые и короткие названия использовать для типов из общих сценариев

    • чтобы пользователи угадали с первого раза что делает тип, даже если он противоречит иерархии наследования

    • лучшие и наиболее легко распознаваемые названия для наиболее часто используемых типов (даже если название лучше соответствует другому редко используемому типу)

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

    • это увеличивает "видимость" и находимость юзером типов из основных сценариев

  • общий api должен включать в себя 5-10 основных сценариев

  • в самых основных сценариях должны создаваться объекты только одного типа

  • следует оптимизировать производительность основного api

  • можно заимствовать названия и общую часть сценариев из других общеизвестных api, фреймворки и библиотек

    • тогда пользователь уже будет знать как как обращаться с новым api по аналогии с уже известным ему другим api

    • копировать только общие части api, уникальные должны быть своими

  • общий api должен быть простой

    • следует сделать так чтобы простые сценарии api можно было использовать и БЕЗ ДОКУМЕНТАЦИИ

    • разработчик должен смочь начать НЕМЕДЛЕННО применять api

    • самоочевидный api не требует знания взаимодействия множества объектов внутри

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

  • следует удостовериться что компоненты поддерживают паттерн create-set-call

    • юзеры должны быть в состоянии закодить большинство сценариев через этот паттерн

  • не должно быть обширной инициализации(и конфигов) для основных сценариев
    тк они поднимают порог входа и требуют знания подводных взаимосвязей

2) РАСШИРЕННЫЕ СЦЕНАРИИ
  • редко используемые типы должны быть помещены в отдельные подпакеты

  • низкоуровневые api также полностью должен дублировать функционал высокоуровневого api чтобы разработчик мог выбирать

  • сложные вещи должны быть возможными с помощью расширенного api

    • хотя бы если не легкими ,как например в основных сценариях, то хотя бы возможными

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

CONVENTIONS
  • наличие классов открытых для настройки является одним из основных отличий инфраструктуры от библиотеки

  • различные фреймворки/библиотеки/модули должны быть сочетаемы между собой

  • библиотеки должны быть "скучными" (только основной функционал)

  • нельзя требовать чтобы юзеры делали что-либо помимо кода

    • например конфигурировали компоненты в файле конфигурации, генерировали файлы ресурсов

    • пример антипаттерна: IoC конфигурируемый в файле конфигурации Spring Framework или Spring boot

  • разбить функции на отдельные типы так чтобы свойства/методы не перекрывались

пакеты

NAMING
  • названия пакетов в иерархии

    • company.product|technology.feature.subnamespace

    • каждое слово с маленькой буквы

    • компания это зарегестрированное в интернете доменное имя обеспечивающее уникальность в мире

      • используется обращенное доменное имя (те не vova.com а в обратном порядке com.vova)

      • дефис в одной из частей доменного имени следует заменить на подчеркивание

      • если одна из частей доменного имени начинается с цифры (или небуквенного символа) то следует в ее начало добавить подчеркивание

      • если одна из частей доменного имени совпадает с одним из ключевых слов java то следует к нему в конец добавить подчеркивание

    • название продукта должно быть устойчивым технологическим а не сиюминутное маркетинговое (тк оно в пространстве имен уже останется навсегда)

    • Разработчикам следует принять меры, чтобы избежать возможного совпадения имен двух опубликованных пакетов, выбирая уникальные имена пакетов для широко распространяемых пакетов.

  • имя пакета должно отличаться от типа/класса

CONVENTIONS
  • в пакете НЕ должно быть меньше 5 типов

  • типы из одного сценария должны находиться вместе в одном пакете

    • чтобы НЕ импортировать большое количество пакетов

  • в главном пакете НЕ должно быть глубокой иерархии, множества уровней тк юзеру их все придется долго учить

5) методы

NAMING
  • ВСЕ правила актуальны только для public членов

    • для private методов следует использовать суффикс _

  • имена методов начинаются с маленькой буквы, верблюжий стиль

  • методы именуются глагольными фразами

    • первое слово можно сделать глаголом в активной форме

    • таким образом методы отличаются от названий типов - существительных

  • названия методов и свойств должно быть длинным, описательным и подробным

    • тк сейчас такие идентификаторы все равно набираются автозавершениемм ввода

    • именовать в соответствии с решаемой задачей, а не деталями реализации

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

  • стандартные префиксы имен методов:

    		- compute означает что операция долго вычисляется и кешируется
            	- find означает что это простой метод поиска с минимальным количеством вычислений
           	- initialize означает что устанавливается объект/концепция
    	  	- assert для методов проверки объекта, вызывающих иксепшен
    	  	- метод, возвращающий длину чего-либо, должен иметь имя length, как в классе String.
    	  	- метод, проверяющий логическое условие V,связанное с объектом (или запрашивающий состояние объекта), должен иметьимя isV. Примером может служить метод is Interrupted класса Thread.
    	 	- with возвращает копию объекта с одним измененным элементом
    	 	- to конвертирует объект в другой тип
    		- as возвращает представление объекта, имеющее иной тип чем сам объект
    		- ..Value возвращает простой тип, с тем же значением что и у объекта
    		- at комбинирует этот объект с другим
    		- format форматированный вывод
    		- plus возвращает копию объекта с добавкой
    		- minus возвращает копию объекта с вырезкой
    	 	- метод, преобразующий объект в некоторый формат F, должен иметь имя toF. Примерами таких методов являются метод toString класса Object и методы toLocaleString и toGMTString класса java. util. Date.
    		- методы sin и cos класса java . lang.Math имеют удобные математические имена, несмотря на то что эти имена нарушают приведенные ниже соглашения (они являются короткими словами, не являющимися глаголами).
  • имя метода не должно включать имя типа

    • тк это избыточно и тип и так виден

    • например line.getLength() а не line.getLineLength();

  • булевы свойства и функции должны иметь смысл в качестве английской фразы

    • например if(collection.Contains(item) лучше чем if(collection.IsContained(item))

    • например if(expression.Matches(text) лучше чем if(expression.Match(text)

  • не рекомендуются вложенные if

    • тк очень плохо читаемы

    • вместо них лучше else if , switch

  • симметричные методы следует называть симметрично

            - get/set
            - add/remove
            - create/destroy
            - start/stop
            - insert/delete
            - increment/decrement
            - old/new
            - begin/end
            - first/last
            - up/down
            - min/max
            - next/previous
            - old/new
            - open/close
            - show/hide
            - suspend/resume
CONVENTIONS
  • если метод только обеспечивает доступ к значению то вместо него должно быть свойство (геттер/сеттер)

  • операцию возвращающую внутреннее состояние лучше делать методом а не свойством

  • операция возвращающая каждый раз другой результат даже при неизменных параметрах лучше делать методом а не свойством

    • Например Guid.NewGuid() , DateTime.Now()

перегрузка

NAMING
  • следует понятно называть дополнительный параметр перезагружаемого метода
    так чтобы было видно, какое значение по умолчанию будет при отсутствии данного параметра
    (в другой версии этого же перезагружаемого метода)

    • например из названий method(name) и method(name, ignoreCase) видно что первый метод игнорирует регистр

  • следует одинаковые параметры в разных перезагружаемых методах называть одинаково

    • также они должны находиться в том же самом порядке от начала

CONVENTIONS
  • не рекомендуется делать две перезагруженных функции с одинаковым числом параметров, т.к. запутает юзеров

    • вместо перезагрузки лучше дать несколько разных названий. например writeBoolean(boolean), writeInt(int), and writeLong(long)

    • избежать перегрузки конструкторов можно заменив их на фабрики с различными именами

  • если все таки хочется сделать перегрузку с одинаковым числом параметров

    • то вместо этого лучше чтобы хотя бы один параметр различался типом
      так чтобы их нельзя было привести один к другому

переопределение

NAMING
  • следует одинаковые параметры в исходном и переопределенном методах называть одинаково

  • название абстракт метода = название public метода + суффикс Core

    • нельзя использовать устаревший суффикс Impl

  • везде используйте аннотацию @Override

CONVENTIONS
  • наследование можно безопасно использовать внутри пакета (те с package уровнем доступа), где реализация и подкласса, и суперкласса находится под контролем одних и тех же программистов.

  • наследование нарушает инкапсуляцию тк, правильное функционирование подкласса зависит от деталей реализации его суперкласса.

  • по умолчанию обычный класс (не предназначенный для наследования) лучше делать final или со всеми private конструкторами

ФИНАЛИЗАЦИЯ МЕТОДОВ
  • рекомендуется финализировать юзерские методы переопределенные от abstract

    • особенно если я не планирую здесь думать о расширяемости дальше

  • как только я финализирую метод, я могу избежать побочных эффектов связанных с наследованием моего юзерского класса

  • можно все public методы сделать final, а шаблонные методы сделать abstract

    • как альтернатива паттерну "вызов private helper версий методов из public"

РАСШИРЯЕМОСТЬ ABSTRACT МЕТОДАМИ
  • abstract метод обеспечивает принудительную расширяемость

  • если сделать метод abstract то юзер точно должен его отнаследовать

  • следует тщательно задокументировать

    • тк юзеру нужно подробно объяснить как их нужно заместить,
      объяснить юзеру что такое паттерн шаблонный метод и тп

  • следует тщательно запроектировать:

    • те должен быть ясный сценарий, требующий расширяемости,

    • те вы уже придумали как юзер должен их использовать

  • нельзя делать члены abstract, если нет серьезного основания и вы учитываете стоимость проектирования, тестирования, поддержки abstract методов

  • изменение abstract методов в будущем чревато большим риском нарушения совместимости

  • следует делать abstract методы также и protected

РАСШИРЯЕМОСТЬ PROTECTED МЕТОДАМИ
  • protected метод обеспечивают расширяемость не усложняя основной public интерфейс

  • protected методы надо проектировать так же тщательно как и public

    • те внутри надо обеспечивать такой же уровень проверок и документирования

  • не рекомендуется делать public методы абстрактными

    • тк фишка паттерна как раз в том чтобы разнести public и abstract методы

РАСШИРЯЕМОСТЬ ABSTRACT PROTECTED МЕТОДАМИ
  • abstract методы создаются protected только для расширения НО не для прямого использования юзером

ПАТТЕРН ШАБЛОННЫЙ МЕТОД
  • следует делать ABSTRACT только самую длинную перегрузку

    • все более короткие версии будут использовать эту длинную версию

  • шаблонный метод следут делать PROTECTED abstract чтобы расширяемость была управляемой

    • те его нельзя было вызвать напрямую извне но нужно было переопределить

  • в коротких неперезагружаемых версиях будет реализована вся скучная логика проверки входных параметров

    • такие методы можно сделать public final

  • рекомендуется ограничить расширяемость только тем что АБСОЛЮТНО НЕОБХОДИМО с помощью шаблона template method

  • следует использовать этот паттерн для достижения управляемой расширяемости тк юзер должен переопределить только некоторые методы

  • нельзя делать перегрузку abstract метода

    • тк будет уже два абстрактных метода и их оба придется наследовать одновременно,
      а пользователь может этого не знать и переопределить только один метод

  • рекомендуется проверять параметры в public методе чтобы не проверять их каждый раз в abstract методах

ПАТТЕРН КАСКАД ФАСАДОВ
  • паттерн используется для централизации логики в одном месте

  • можно использовать лесенку перезагрузок: метод использует вызов следующего перегруженного с +1 параметром

    • для централизации логики в одном месте

  • паттерн можно заменить на паттерн Builder

  • самый последний метод в лесенке может быть как раз protected abstract шаблонным методом

    • с самым большим количеством параметров

ПАТТЕРН PIRIVATE/FINAL HELPER МЕТОДОВ ВЫЗЫВАЮЩИХСЯ ИЗ МЕТОДОВ (КОТОРЫЕ БУДУТ ПЕРЕОПРЕДЕЛЯТЬСЯ)
  • используются в классах спроектированных для наследования, чтобы детали реализации не утекли в подклассы

  • в классе для наследовния используйте helper method’ы

    • для каждого открытого метода создайте закрытый аналог(helper method)

    • внутри каждого открытого метода должны быть только вызовы зарытых аналогов

    • helper method’ы должны вызывать только друг друга, а не открытые методы чтобы избежать вызова отнаследованного класса

ПАТТЕРН ЗАМЕНЫ НАСЛЕДОВАНИЯ НА КОМПОЗИЦИЮ
  • просто наследование базового класса заменяется на наследование композиции (имплементирующей интерфейс) с переадресацией

    1. forwarding class: класс переадресации имплементирует общий интерфейс и содержит ссылку на базовый класс

      и все методы переадресует базовому классу
      			public class ForwardingSet<E> implements Set<E> {private final Set<E> s;
    2. класс декоратор: наследует класс переадресации

      и как декоратор добавляет некий дополнительный функционал
      			public class InstrumentedSet<E> extends ForwardingSet<E> {
      			public InstrumentedSet(Set<E> s) {super(s);}
  • этот подход будет работать с любым ранее существовавшим конструктором.

    • тк принимает объект базового класса то не надо переопределять все его конструкторы

  • предохраняет от неожиданного расширения базового класса т.к. метод подкласса в таком случае тоже нужно было бы оверрайд (а в варианте с композицией в ней остались бы все те же методы и к новому методу базового класса доступа бы не было)

    • но НЕ предохраняет от неожиданного расширения интерфеса default методами

  • минус в том что такие классы-оболочки не приспособлены для использования в схемах с обратным вызовом (callback), где один объект передает другому объекту ссылку на самого себя для последующего вызова. Поскольку «обернутый» объект не знает о своей оболочке, он передает ссылку на самого себя, и, как следствие, обратные вызовы минуют оболочку. Это называется проблемой самоидентификации (SELF problem)

ПЕРЕОПРЕДЕЛЕНИЕ МЕТОДА EQUALS, HASHCODE, COMPARETO
1) СЛЕДУЕТ обязательно переопределять данный метод для НЕМУТАБЕЛЬНЫХ классов-значений
  • следует сравнить все свойства по значению

  • для такого сравнения следует употреблять термин "meaningfully equivalent" вместо слова "equal."

  • не рекомендуется использовать если реализация получается медленной

  • Если класс реализует интерфейс, который уточняет соглашения для метода equals, то в качестве типа указывайте этот интерфейс, что позволит выполнять сравнение классов, реализующих этот интерфейс.

    • Если на тип был определен как интерфейс, вы должны получать доступ к значимым полям аргумента, используя методы самого интерфейса.

2) НЕЛЬЗЯ переопределять данный метод для МУТАБЕЛЬНЫХ классов основываясь на равенстве всех свойств
  • тк как только одно из свойств изменится то равенство сразу потеряется

  • также объект потеряется сразу в хэш таблицах тк хэш объекта изменится

  • обычно по-умолчанию equals смотрит на равенство ссылок и многие юзеры удивятся, когда поведение по умолчанию изменено на равенство значений

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

  • для мутабельных классов (например DDD агрегатов) следует переопределять данный метод отталкиваясь от равенства свойства выбранного для определения идентичности

    • рекомендуется убедиться что hashcode возвращает точно то же самое значение независимо от любых изменений сделанных с объектом

  • следует и перезагружать и переопределять equals

    • например будет два метода equals(object p) вызывающий equals(MyClass p)

  • каждый раз когда реализую IComparable следует переопределять equals

    • но наоборот делать необязательно: те когда делаю equals не всегда нужен comparable

    • если есть неконсистентность с equals то ее нужно явно прописать в джавадоке

  • equals и hashCode следует переопределять одновременно

    • эти оба контракта взаимозависимы и они оба используются главным образом для поиска

    • это гарантирует что два объекта которые рассматриваются как равные, имеют один и тот же хэш-код

    • если объекты НЕ равны то они ТОЖЕ могут иметь один и тот же хэш код

  • рекомендуется убедиться что если equals для каких нибудь двух объектов возвращает true,
    то hashcode возвращает одно и то же значение для этих объектов

  • следует гарантировать что hashcode генерирует случайное распределение чисел для всех объектов данного типа

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

  • нельзя вызывать исключения из equals и hashCode

    • из equals лучше возвратить false чем вызвать исключение

  • на немутабельном типе можете закешировать хэш-код в самом этом объекте вместо того, чтобы вычислять его всякий раз заново, как только в нем появится необходимость

  • кстати на неизменяемых ссылочных типах тяжело заметить различие между равенством ссылок и равенством значений

    • не надо переопределять equals для класса с объектным пулом те классом управляющим своими экземплярами так как будет не более одного экземпляра с данным значением (например для перечислений) и их можно сравнивать ==

  • при переопределении[некого моего класса] метода equals следует вызвать super.equals

  • при переопределении[некого моего класса] метода compareTo следует выбрать одну из двух стратегий

    1. не переопределять метод и сделать метод базового класса как final

    2. внутри обоих методов сделать проверку на абсолютное равенство типу сравниваемого класса, чтобы сравнение было симметрично для тех случаев когда сравниваются объект подкласса с объектом суперкласса

    3. можно вообще не переопределять этот метод а сортировать элементы используя Comparator, или использовать упорядоченные коллекции которым передается на вход Comparator (TreeSet, TreeMap, PriorityQueue) вместо Comparable элементов

1) в equals порядок сравнения НЕважен
  • Чтобы добиться наилучшей производительности, вы должны в первую очередь сравнивать те поля, которые будут различаться с большей вероятностью, либо те, которые сравнивать проще.

  • если значение избыточного поля равнозначно суммарному описанию объекта в целом, то сравнение подобных полей позволит вам сэкономить на сравнении действительных данных, если будет выявлено расхождение.

2) в compareTo порядок сравнения важен
  • Если у класса есть несколько значимых полей, порядок их сравнения критически важен.

  • Вы должны начать с самого значимого поля и затем следовать в порядке убывания значимости.

ПЕРЕОПРЕДЕЛЕНИЕ МЕТОДА CLONE (ИНТЕРФЕЙС CLONEABLE)
  • t-f: не рекомендуется переопределять и вообще использовать данный метод

    • вместо него нужно использовать глубоко-копирующий конструктор или фабрику

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

    • если поставить классу интерфейс-маркер Cloneable то метод Object.clone станет для него public

    • при условии что в каждом классе в методе Clone нужно вызывать super.clone что в итоге приведет к вызову Object.clone (это непринудительное сцепление конструкторов)

    • получается что объект не создается явно с использованием конструктора, а создается внутри Object.clone

    • для мутабельного объекта с мутабельными свойствами следует сделать рекурсивный вызов метода clone для всех свойств

  • следует явно написать в JavaDoc что это глубокая или поверхностная копия

  • типы копирования

    1. deepCopyOf глубокое копирование, копирует объект и рекурсивно те все объекты в графе

    2. copyOf поверхностное копирование, копирует только часть графа

ПЕРЕОПРЕДЕЛЕНИЕ МЕТОДА TOSTRING
  • предназначен только для целей отладки

    • каждый разработчик может менять данный метод как хочет, поэтому этот метод нестабилен

  • не предназначен для вывода конечному пользователю в UI

    • для этого нужно делать свои отдельные методы

  • следует переопределять данный метод тк заданная по умолчанию реализация не очень полезна

  • надо строку возвращаемую из toString делать максимально короткой

    • не больше чем длина одного экрана помещаемая в дебагере (80 символов)

    • для больших объектов или объектов, состояние которых трудно представить в виде строки. В таких случаях метод toString должен возвращать такие резюме, как «MyClass (1487536 peoples)»

  • рекомендуется возвращать уникальную строку, связанную с данным экземпляром класса

    • те хэш-код или адрес в памяти

  • надо выводить более понятные имена свойств, в случае если их настоящие названия нечитаемы

  • cледует выводить локализованную строку, зависящую от региона

    • в случае форматирования денег и дат

    • надо отформатировать строку в соответствии с текущими региональными настройками Locale.getDefault()

  • нельзя возвращать пустую строку или null

  • нельзя вызывать исключения внутри метода

  • нельзя чтобы у toString были побочные эффекты

    • тк это одна из причин по которой ее вызывают отладчики иначе это может сильно затруднить отладку

  • рекомендуется выводить значение, которое можно разобрать синтаксическим анализатором для данного типа

    • на типе сделать комплиментарный метод MyType.parse(obj.toString()) для разбора строки обратно в объект

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

  • следует учесть секьюрити и безопасность того что информация может попасть не в те руки тк данное сообщение может иногда попасть в UI

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

переопределение event handler-ов

МЕТОД - ОБРАБОТЧИК СОБЫТИЯ
  • для возможности вызова юзерского кода следует использовать параметр-лямбда метода или лямбда свойство, вместо переопределения этого abstract метода

  • метод следует сделать protected abstract он будет вызываться при наступлении события

    • подкласс обязательно должен его будет переопределить для того чтобы обработать событие

  • метод должен называться OnИмяСобытия

  • ?метод принимает на вход один параметр "е" типа подкласса EventArgs

    • его всегда следует проверять на null в методе

тело метода и возврат значений

NAMING
  • односимвольных имен локальных переменных или параметров следует избегать, за исключением временных переменных и переменных циклов или когда переменная содержит неважное значение. Обычно используемыми односимвольными именами являются:

  • временные переменные имеют область видимости в несколько строк кода

    		b - для переменной типа byte;
    		с - для переменной типа char;
    		d - для переменной типа double;
    		е - для исключения Exception;
    		f - для переменной типа float;
    		i, j и k - для целочисленных значений;
    		l - для переменной типа long;
    		о - для переменной типа Object;
    		s - для переменной типа String;
    		v - для произвольной переменной некоторого типа.
    		T - type 	          для генерик класса
    	        E - element	для генерик метода
    	        R - return		для генерик класса/метода - возвращаемое значение
  • самый сильный прием сужения области видимости локальной переменной заключается в декларировании ее в том месте, где она впервые используется

    • тк если программа совершенствуется и переменная больше не нужна, легко забыть убрать ее декларацию, если та находится далеко от места первого использования переменной

  • чтобы уменьшить область видимости локальных переменных можно разбить метод на два

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

  • рекомендуются аббревиатуры, т.е. первые буквы последовательностей слов,

    • например как ср для переменной, хранящей ссыпку на объект класса ColoredPoint.

  • рекомендуются сокращения

    • например, переменная buf для хранения указателя на буфер некоторого вида

  • рекомендуются мнемонические термины, организованные таким образом, чтобы облегчить запоминание и понимание, обычно при использовании наборов локальных переменных по образцу имен полей распространенных классов, например:

    • in и out, некоторые виды ввода и вывода;

    • off и len, представляющие смещение и длину.

  • длинные локальные переменные следует делать в нижнем регистре с префиксами чтобы они отличались от camel-стиля свойств

    • например v_a_r

  • не рекомендуется объявлять несколько локальных переменных в одном операторе объявления

    • чтобы код было удобнее читать

  • не смотря на то что, отдельными частями оператора цикла for могут быть практически любые выражения, рекомендуется чтобы все три части должны только инициализировать, проверять и обновлять один и тот же счетчик

    • если не придерживаться этих правил то полученный код станет неудобным, а то и вовсе непригодным для чтения

  • рекомендуется foreach вместо for

    • тк в for Итератор встречается трижды в каждом цикле и переменная индекса четыре раза, что дает большую возможность использовать неверную переменную

    • рекомендуются стримы вместо foreach
      тк в них есть фильтрация, преобразование и параллелья итерация

CONVENTIONS
  • рекомендуется несколько return в коде метода

  • нельзя возвращать null, верните вместо этого пустую коллекцию

    • те юзеру приходится писать дополнительный код проверки на ноль и юзер может забыть проверить на нуль

    • в качестве пустой коллекции лучше брать уже имеющиеся синглетоны Collections.emptyList()

  • рекомендуется подумать про допустимость повторных вызовов метода

    • например, поставить в начале метода проверку на то что метод уже вызывался

  • ???основная работа в программе выполняется путем вычисления выражений

  • рекомендуется StreamAPI вместо ForEach

RETURN OPTIONAL
  • если невозврат значения является штатной работой метода, то лучше возвращать Optional.empty

  • лучше не использовать его для возврата значений из get-еров

  • не использовать его в случаях где метод возвращает коллекцию тк в этом случае лучше вернуть пустую коллекцию

    • избегайте двойной запаковки: другие контейнеры лучше не заворачивать в Optional тк они и сами могут быть пустыми как Optional

RETURN COLLECTION
  • предпочитайте использовать немутабельные версии в возвращаемых значениях

  • рекомендуется создавать свои коллекции, наследуемые от базовых

  • нельзя возвращать null, верните вместо этого пустую коллекцию

    • тк пользователи будут ожидать объекта

  • предпочитайте использовать Map если если у элементов хранимых в коллекции есть уникальные ключи

  • коллекции, зависящие от внешних объектов, рекомендуется возвращать в виде копии

    • например файлы на диске представляют собой внешний объект

    • коллекцию, которая оперативно онлайн отслеживала бы изменения файлов на диске трудно или невозможно реализовать

6) свойства классов

bool

NAMING
  • называть свойство в утвердительной форме

    • например isError вместо isNoError

    • например isFound вместо isNotFound

  • в геттере вместо get следует сделать префикс is

    • можно также использовать префикс can, has, should

    • сеттеры называются как обычно

  • следует выбирать значимые имена

    • не следует использовать общие: isStatus isFlag

CONVENTIONS
  • лиг без: они также упоминаются на жаргоне как:

    	- object fields
    	- instance variables
    	- instance attributes
    	- class variables
    	- static variables
    	- static attributes

collection

NAMING
  • имена свойств должны быть во множественном числе

    • это позволит юзеру отличить свойства коллекций от обычных свойств

CONVENTIONS
  • в качестве типа свойства рекомендуется использовать: (в порядке приоритета)

    1. свои коллекции, наследуемые от нижеследующих базовых

    2. List<>,Set<>,Map<>,Queue<> наиболее подходящие для решаемой задачи

    3. Collection<> или Iterable<> если я пишу фреймворк

  • предпочитайте использовать немутабельные версии коллекций в final свойстве

  • свойства всегда должны возвращать оперативную коллекцию (а не копию - моментальный снимок)

    • тк создание копии стоит дорого

  • свойство никогда не должно возвращать null

    • должно быть проинициализировано по умолчанию пустой колекцией

  • свойство должно быть final ИЛИ только с геттером

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

    • вместо сеттера можно выставить метод AddRange для добавления элементов

constant

NAMING
  • имена констант немутабельных объектов пишутся большими буквами со знаком подчеркивания между словами

    • например ПЕРЕМЕННАЯ_ПО_УМОЛЧАНИЮ

  • имена связанных между собой констант должны иметь общий префикс

    • например: COLOR_RED COLOR_GREEN COLOR_BLUE

CONVENTIONS
  • в java формально константами считаются:

    • static final примитивные типы

    • static final объектные типы, которые никогда не пишутся с точкой

    • static final массивы, которые никогда не пишутся с точкой и [ те по-простому или примитивное значение или ссылка на немутабельный объект

  • сам экземпляр константного объекта должен быть немутабельного типа

    • чтобы его нельзя было поменять

  • избегайте использовать магические числа в коде, вместо них всегда используйте константы

    • тк они все будут в одном месте

    • тк они улучшат читаемость кода

  • рекомендуется использовать перечисления вместо статических констант

    • улучшают проверку ошибок во время компиляции

    • улучшают удобочитаемость тк все в одном пакете

    • автоподстановка IDE

  • общие константы можно поместить во вспомогательный класс (с private конструкторами чтобы его нельзя было создать)

    • не рекомендуется помещать константы в интерфейс тк самое неправильное -это имплементировать такой интерфейс тк потом нельзя будет избавится от "implements IConst" -?? сам класс и методы public но они не будут выставляться модулями jigsaw

  • рекомендуется вместо констант использовать функции возвращающие константное значение

    • чтобы более гибко потом можно было поменять возвращаемое значение

  • для констант следует использовать public final поля

    • для предопределенных экземпляров следует использовать private STATIC final поля + get-тер/функицию для получения значения

    • ?или же лучше отдельную фабрику дефолтных экземпляров

    • ?таким образом дефольный объект синглетон будет всего один

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

getset

NAMING
  • ВСЕ правила актуальны только для public членов

    • для private свойств следует использовать суффикс _

    • можно открывать(делать public) поля приватных классов(которые никто извне не увидит) и приватных вложенных классов

  • имена свойств начинаются с маленькой буквы, верблюжий стиль

  • в двубуквенных акронимах обе первых буквы маленькие

    • например ioStream

  • для имени свойства использовать существительные, именные группы или прилагательное

  • следует называть свойство в утвердительной форме

    • например CanSeek вместо CantSeek

  • действительный залог лучше чем страдательный

    • CanSeek вместо IsSeekable

  • ?рекомендуется давать свойствам то же название что и их типам

    • например Color color

    • это экономит значимые имена

  • не использовать префикс get set для имени свойства, а только для имени метода

  • префикс numberOf для переменных представляющих количество объектов

    • не использовать префикс N Num

  • использовать суффикс No для обозначения переменных представляющих номер сущности

    • примеры: tableNo, employeeNo

CONVENTIONS
  • tf: под свойствами понимаются private поля выставленные с помощью сеттеров/геттеров (accessor/mutator)

    • все поля должны быть private, никогда не следует выставлять поля как public

    • под полем подразумевается свойство класса

  • простые свойства создавать только для чтения (final и только геттер), если прога не должна менять значение свойств

    • под простыми имеются ввиду немутабельные value-типы (не ссылочные типы)

    • но если в свойстве содержится мутабельный тип, то выставление только геттера не поможет, тк сам тип можно поменять

  • нельзя выставлять для свойства только сеттер

    • нужно для пары также выставить паблик геттер

  • нужно все равно сохранять значение даже если сетер вызывает исключение

    • те даже если данное свойство не прошло проверку в сетере то все равно его установить, или сохранить значение в другое место

  • нельзя вызывать исключения из гетеров

    • гетеры должны быть простыми операциями и не должны иметь постусловий

    • если он может вызывать исключение то его следует сделать методом

  • долгие операции должны быть методом а не свойством

    • например выполняющие доступ к сети или файлам

    • свойства используются для простого доступа к простым данным с простыми вычислениями

  • следует использовать свойство если логически по смыслу это атрибут типа

  • НЕ используйте Optional как свойство класса

ПАТТЕРН NEW-SET-CALL В МУТАБЕЛЬНЫХ КЛАССАХ
  • это такой паттерн в котором класс проектируется с

    • с дефолтным или относительно простым конструктором

    • большим количеством свойств задаваемых через сеттеры

    • и последующему вызову простых методов экземпляра класса без аргументов или малым количеством аргументов тк все уже заранее задано в свойствах

      1) данный паттерн подразумевает
  • задание большого количества свойств объекта управляющих семантикой вызовов методов, причем можно устанавливать каждое свойство отдельно

  • и небольшое количество параметров в самих методах

  • методы должны предоставлять действия и свойства данные для этих действий

    2) недостаток в том что нельзя использовать метод параллельно, сам объект можно использовать только для одного запроса тк параметры задаются свойствами объекта
    3) плюс в том что может быть одна версия метода работающая с разным набором свойств, в зависимости от того какие свойства заданы
  • для юзеров важно что методы простые тк значительно труднее изучить назначение каждого параметра метода

  • следует всем свойствам давать значения по умолчанию

    • тк у юзера должна быть возможность вызывать методы объекта созданного дефолтным конструктором без параметров

      4) с каждой новой возможностью можно лишь добавить новое свойство а метод оставить тем же (внутри он будет учитывать эту новую возможность)
  • cледует давать возможность задавать свойства в любом порядке

    • это может привести к проблемам в виде недопустимого состояния объекта при вызове метода

    • выгоды этого паттерна превышают эти недостатки (при использовании в основных сценариях для юзеров главное это удобство и простота использования)

    • проблемы должны быть смягчены исключениями/сообщениями об ошибках в методах

      5) В сеттере можно присвоить null, но вывожу сообщение об этом в лог
  • следует проверять целостность состояния объекта уже в CALL-методах, а не в конструкторах

  • свойства обеспечивают естественную возможность самодокументирования

  • компоненты должны главным образом полагаться на свойства и составные части как средства изменения их поведения

ПАТТЕРН NEW-CALL В неМУТАБЕЛЬНЫХ КЛАССАХ
  • это такой паттерн в котором класс проектируется с

    • сложными методами которым передается большое количество параметров при вызовах

      1) данный паттерн подразумевает
    • задание большого количества параметров в методах, причем все параметры следует заполнять сразу все при каждом вызове (см главу параметров: количество параметров не более 4-х)

    • и небольшое количество свойств объекта

      2) плюс в том что можно вызывать такие методы параллельно
      3) минус в том что при данном паттерне получается большое количество перегруженных методов
      4) и с каждой новой возможностью вы добавляете все больше перегрузок метода
    • операции преобразования и трансформации лучше делать методами с параметрами

    • следует проверять целостность состояния объекта в конструкторах а не в CALL-методах

lambda

NAMING
1) pre-events пред-события
  • для имен используется настоящее время глагола

  • вызываются прежде чем будет иметь место побочный эффект

  • Пример: Closing событие перед закрытием окна,

2) post-events пост-события
  • для имен используется прошедшее время глагола

  • вызываются после того как произошел побочный эффект

  • Пример: Closed событие после закрытия окна

не следует использовать префиксы/постфиксы before/after

.CONVENTIONS

  • для возможности вызова юзерского кода следует использовать параметр-лямбда метода, вместо переопределения этого abstract метода

private (fields)

NAMING
  • к полю/свойству класса, не выставленному через public геттер/сеттер, нужно прибавить суффикс в виде черточки

    • например someProp_, это помогает отличить внутренние свойства от выставленных наружу через геттер/сеттер

CONVENTIONS
  • все поля должны быть private, никогда не следует выставлять поля как public

    • под полем подразумевается свойство класса

    • свойство выставляется только через гетер и сетер

7) типы

NAMING
  • ВСЕ правила актуальны только для public членов

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

  • в двубуквенных акронимах обе первых буквы заглавные

    • например IOStream

  • избегайте акронимы

    • можно использовать общеизвестные акронимы XML,IO,HTML

    • можно использовать: Ok, Id

  • трех и более буквенные акронимы считаются обычным словом

    • например ProcessHtmlTag, htmlTag

  • старайтесь НЕ использовать общие названия

    • но иногда можно использовать в случаях когда в имени нет особой смысловой нагрузки а тип неважен

    • пример общих названий: value, item, element, node, log, message

    • вам следует конкретизировать данные названия: FormElement, XmlNode, EventLog, SoapMessage

    • слова не должны иметь двойного трактования чтобы избежать побочных эффектов

    • юзеры в своих сценариях наверняка будут использовать эти названия что приведет к конфликту имен

  • порядок слов выбирать чтобы из всех вариантов было наиболее легко читаемым

    • например HorisontalAlignment vs AligmentHorisontal

  • удобочитаемость должна быть в ущерб краткости

    • пример CanScrollHorisontally vs ScrollableX

  • названия должны соответствовать бизнесу или сценариям а не технологиям или паттернам

    • названия должны отражать сценарии использования а не иерархию наследования

  • имена типов могут совпадать для разных прикладных моделей

    • но не должны совпадать с именами типов основных фреймворков java core

    • не должны совпадать с ключевыми словами языка программирования java

CONVENTIONS
  • TDD рекомендуется сначала сгенерировать тест

    • объектная модель вытекает из примеров кода

классы

NAMING
  • имя класса должно быть существительным или именной группой

  • можно добавить суффикс в виде названия базового класса

    • тк это очень читаемо и явно объясняет отношения

    • рекомендуется для типов наследованных от типов стандартного джава фреймворка

РАСШИРЯЕМОСТЬ
  • для расширяемости лучше подходят обычные классы

    • не final

    • без abstract методов

    • без protected методов

  • расширяемость должна проектироваться заранее

    • тк юзеры могут расширить способами которые архитекторы предвидеть не могли:

    • поэтому специально для расширяемости рекомендуется использовать интерфейсы и абстрактные классы

  • финализация это механизм предотвращения расширяемости

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

    • следует финализировать методы и классы, важные с точки security

  • у класса всегда можно убрать final, но обратно уже добавить нельзя

  • нельзя в final классе делать методы protected или abstract

    • тк подразумевается что final классы не наследуются

abstract

NAMING
  • ??добавить префикс Abstract для абстрактных классов

CONVENTIONS
  • такие классы предназначены для продвинутых расширенных сценариев, и потому неинтересны большинству юзеров

    • такие классы должны быть в отдельном неймспейсе, например подпакете основных сценариев

РАСШИРЯЕМОСТЬ
  • нельзя требовать чтобы юзеры: наследовали классы или имплементировали интерфейсы

    • где возможно использовать вместо этого лямбды

  • вместо абстрактных классов с частично реализованной логикой рекомендуется эту логику поместить в default методы интерфейсов

  • специально созданы для расширяемости через наследование

  • такие классы сами не подходят на роль интерфейсов тк содержат слишком большой объем реализации

  • должны сопровождаться полным набором справочной документации описывающей необходимую семантику типов реализующих контракт

    • первоклассная документация должна ясно и обстоятельно объяснить все пред- и пост- условия имплементирования

    • в противном случае пользователи откажутся от использования типа

  • нельзя делать public конструкторы тк нельзя создавать экземпляры абстрактного класса

    • наличие конструктора только запутает

    • следует сделать конструктор protected

  • абстрактный класс здесь неприменим для моделирования множественного наследования

ПАТТЕРН SKELETAL IMPLEMENTATION CLASS (ШАБЛОННЫЙ МЕТОД)
  • помогают имплементировать интерфейсы чтобы юзеры получили часть реализации

    • например если в корне иерархии типов интерфейс:

    • List → AbstractList(*) → MyList

    • тк они добавляют еще один уровень иерархии то они усложняют инфраструктуру

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

ПАТТЕРН SIMPLE IMPLEMENTATION CLASS
  • List → SimpleImplementationClass

  • для каждого абстрактного класса нужно сделать минимум одну имплементацию

    • это позволяет проверить правильность дизайна и вовремя исправить ошибки

  • Уменьшенный вариант скелетной реализации

  • Это простейшая возможная работающая реализация

collection

NAMING
  • добавить к имени суффикс

    • Collection

    • Map

    • List

    • Set

    • Queue

  • добавить префикс представляющий собой название типа элемента

    • например AddressCollection для Collection<Address>

  • добавить префикс Immutable для немутабельных коллекций

    • например ImmutableAddressCollection для Collection<Address>

CONVENTIONS
  • рекомендуется создавать свои коллекции, наследуемые от базовых тк это позволяет:

    • давать лучшие названия

    • добавлять свои вспомогательные члены

    • в будущем беcпрепятственно расширять реализацию

  • для высокоуровневых api следует предпочитать коллекции, для низкоуровневых следует предпочитать более быстрые массивы

  • использование немутабельных массивов крайне дорого тк придется все время клонировать массив

  • нельзя использовать устаревшие типы коллекций без генериков <T>

    • тк они слаботипизированы что является источником ошибок

  • коллекция должна быть простым типом, предназначенным для управления группой объектов со сходными характеристиками (хранения элементов, доступа к ним и управления ими и не больше)

    • категорически нельзя использовать коллекции для сложных типов не связанных с вышеизложенными простыми действиями

  • типы коллекций:

    1. копия (моментальная копия) - коллекция, представляющая состояние в некоторый момент времени

    2. оперативная коллекция - коллекция которая ВСЕГДА представляет текущее состояние

  • для немутабельных коллекций следует ввести метод bool isReadOnly возвращающий true

  • предпочитайте использовать Map если если у элементов хранимых в коллекции есть уникальные ключи

inline

CONVENTIONS
  • нужно создавать вложенный тип, если нужен класс имеющий доступ к private переменным другого класса

    • тк в этом весь смысл вложенного класса

    • но такие классы нельзя создавать независимо, тк если тип можно создать независимо то это означает что у него есть самостоятельное место в инфраструктуре

  • не рекомендуется вложенный класс делать public

    • тк юзеры не умеют обращаться с такими классами и не будут на них явно ссылаться или явно создавать объекты таких классов

    • юзерам будет виден только интерфейс вложенного класса

  • возвращать экземпляр такого класса в отдельном методе внешнего класса

static

CONVENTIONS
  • используются для хранения методов расширения или функций для которых нет полностью объектной обертки

    • нельзя использовать как разнородный набор дополнений, должна быть четкая концепция расположенных в нем методов

  • рекомендуется использовать только как вспомогательные классы для объектно-ориентированного ядра инфраструктуры

  • все свойства и методы должны быть статическими

    • не должно быть обычных членов

  • нельзя создавать объекты такого класса, может быть только один экземпляр-класса

    • сделать private дефолтный конструктор

    • а также внутрь такого конструктора обязательно поместить AssertionError, чтобы конструктор не был случайно вызван изнутри

    • не должно быть других public или protected конструкторов

    • доступ к статическому свойству/методу класса через экземпляр не приветствуется

  • статик свойства и методы нельзя наследовать

    • нужно пометить явно такой класс final

    • также все методы могу пометить как final

JAVA
  • в джава чисто статическими могут быть только nested static классы

value

NAMING
  • ??добавить префикс Immutable для классов немутабельных объектов

CONVENTIONS
  • (мутабельные)классы с открытыми изменяемыми полями небезопасны в системе с несколькими потоками

  • используются для маленьких простых типов, подобных примитивам int, String

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

  • рекомендуются когда экземпляры маленькие, существуют недолго и внедряются в другие объекты

    • размер экземпляра класса менее 16 байт (если класс большой то следует тщательно продумать: будут ли проблемы при генерации большого количества объектов)

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

  • немутабельные

    • все public свойства должны быть немутабельными те final

      1. если свойства мутабельные то использую защитное копирование/defencive copying на сетерах и гетерах

    • private дефолтный конструктор

    • public только конструкторы присваивающие final свойства

    • только геттеры, сеттеров нет

    • чтобы нельзя было отнаследовать и затереть данные подкласса

      1. вариант а) сделать класс final

      2. вариант б) закрыть ВСЕ конструкторы и получать объекты только фабрикой

    • методы не должны возвращать ошибки, вместо ошибок Optional (лдя того чтобы лучше соответствовать функциональному программированияю )

  • обязательно нужно реализовать equals

    • сравнение по значениям свойств

  • в немутабельных классах используется функциональный подход:

    • операции немутабельного класса могут возвращать новый немутабельный объект

  • часто с немутабельным классом поставляется компаньен: мутабельный класс

    • это нужно для того чтобы избежать проблем производительности и не генерировать много классов-значений

  • не использовать для точных/денежных расчетов float. double, используйте вместо них BigDecimal,int, long ⁃ float. double были сделаны для того чтобы быстро получать правильное приближение для широкого диапазона значений. Однако эти типыне дают точного результата ⁃ у BigDecimal восемь режимов округления и юзер может его контролировать ⁃ Note that BigDecimal’s String constructoris used rather than its double constructor. This is required in order to avoid introducing inaccurate values into the computation. new BigDecimal(".10");

enum

NAMING
  • для имени использовать существительное в единственном числе

    • НЕ использовать суффикс Enum, Flag

ЗНАЧЕНИЯ ПЕРЕЧИСЛЕНИЙ
  • для значений перечисления НЕ использовать какой-либо префикс

    • тк к значениям перечислений и так обращаются всегда с именем самого типа перечисления

  • нельзя использовать зарезервированные значения на будущее

    • тк они только загрязняют и могут приводить к ошибкам

    • я всегда потом могу добавить новое значение

    • я могу использовать вообще перечисление без значений, и добавить значения потом

    • но в сами методы я проверку на будущие значения могу добавить

  • обязательно нужно ввести нулевое значение в перечисление

    • или самое общее значение будет выполнять эту роль

    • назвать его None

CONVENTIONS
1) если обычно свойство устанавливается через сетер то лучше использовать bool
2) но если значение обычно устанавливается через конструктор то лучше использовать enum
  • вместо bool параметров лучше использовать перечисления

    • с булевым параметром легче совершить ошибку

    • например в методе невозможно понять: что означает true "без учета регистра" или "с учетом регистра" ?

  • вместо bool параметров можно использовать chaining methods или chaining method with lambda double value = new TaxCalculator().withTaxRegional() .withTaxSurcharge() .calculate(order); double value = new TaxCalculator().with(Tax::regional).with(Tax::surcharge) .calculate(order);

  • если в сигнатуре метода идут два bool параметра подряд, то их лучше заменить на перечисение

    • тк очень просто совершить ошибку перепутав их местами

  • вместо true/false лучше использовать осмысленные названия значений перечислений

    • перечисления только с двумя значениями прекрасная и обычная практика

  • при передаче значения перечисления в метод, следует проверять правильность перечислимых параметров

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

РАСШИРЯЕМОСТЬ
  • перечисление расширяемо новыми значениями в отличие от bool

  • методы надо проектировать исходя из предпосылки что будут добавляться новые значения перечисления

enumset / битовые перечисления

NAMING
  • для имени использовать существительное в множественном числе

    • те имя должно заканчиваться на s, i, ae

ЗНАЧЕНИЯ ПЕРЕЧИСЛЕНИЙ
  • нельзя создавать значения недопустимые для одновременного использования или противоречащие друг другу

    • для этого лучше тогда создать отдельные перечисления

  • обязательно нужно ввести нулевое значение в перечисление

    • оно должно означать NoneOfAll "все флажки сброшены"

    • представляет собой значение по умолчанию, которое выставляется если ни одно из значений не выставлено

    • нельзя называть None, можно назвать Default или Error

CONVENTIONS
  • полезные дополнительные методы

    • IsExactlyOneBitSet

    • CountOnBits

    • AreAllBitsOn

    • AreAnyBitsOn

    • TurnBitsOnOff

  • для комбинаций флажков следует использовать отдельные значения только если такие комбинации являются общеупотребительными

  • флажковые перечисления следует в методах проверять на недопустимость определенных комбинаций флажков

exception

NAMING
  • добавить к имени суффикс Exception

  • отказ происходит когда метод не может сделать то для чего был спроектирован

    • например, если метод не делает то что предполагает его название, это нужно считать отказом на уровне метода

    • ?таким образом исключение можно назвать CanNotMethodnameException

CONVENTIONS
  • необходимо задокументировать в джавадоках все исключения вызываемые из за нарушения контракта метода

  • такие исключения внешне не должны меняться тк они уже являются частью контракта в джавадоках

  • когда юзер использует основное api, то он не должен получить исключения расширенного api

    • юзер основного api должен получать только исключения основного api

    • для этого низкоуровневые исключения{в высокоуровневых методах} можно оборачивать в высокоуровневые

    • проблема называется loss of failure atomicity и решается через exception translation idiom

  • нельзя использовать исключения для обычного управления потоком

    • те подразумевать что ошибка будет вызываться часто или систематически

    • исключения используются ТОЛЬКО для сообщений об ошибках, проблемах, отказах, нештатных ситуациях

    • исключение дорого тк оно захватывает стек снизу-доверху

  • следует конструктору исключения передавать дополнительную информацию о контексте

    • те нельзя вызывать у исключения конструктор без параметров

    • лучше для этого сделать отдельный конструктор

  • рекомендуется делать исключение Serializable

    • исключения по умолчанию Serializable тк наследуются от Throwable

    • чтобы его можно было делать удаленную отладку и пробрасывать его через удаленное взаимодействие

  • рекомендуется в свой класс иксепшена добавлять гетеры/сетеры для хранения дополнительной информации об ошибке и контексте ошибки

  • иксепшен обязательно должен быть public

    • иначе его невозможно будет перехватить

    • но можно будет перехватить парентовое исключение

  • рекомендуется выносить вызов иксепшена в отдельный метод

    • чтобы сократить код, если иксепшен часто вызывается

      1. (ваиант 1) либо вынести throw new exception

      2. (вариант 2) либо вынести только new exception, это лучше для unsupportedOperationException тк не придется писать обязательный return при отсутствии в методе слова throw

  • нельзя ЯВНО вызывать исключения из finally блоков

    • но неявно можно

  • нельзя вызывать иксепшен внутри класса исключения

  • исключение должно содержать 4 стандартных конструктора

    • public Exception()

    • public Exception(String message)

    • public Exception(String message, Throwable cause)

    • public Exception(Throwable cause)

  • блок finally не должен содержать return

    • тк он перебьет стандартный return в блоке try

СОДЕРЖАНИЕ СООБЩЕНИЯ ОБ ОШИБКЕ
1) сам тип исключения является самой важной информацией
  • на основе типа исключения программа решает что с ним делать

2) исключение должно ясно описывать что нужно сделать чтобы устранить проблему
  • текст сообщения предназначается прежде всего для разработчика

  • конечные пользователи не должны видеть эти сообщения

  • сообщения об ошибках должны помогать разработчикам устранять ошибки в коде

  • обязательно нужно сообщить о неправильном способе использования api

3) сообщение о причине отказа, объясняющее что пошло не так

4) исключение само по себе это документация

  • исключение это лучший вид документации тк многие разработчики предпочитают кодировать методом проб и ошибок а не чтением мануалов

  • рекомендуется вызывать самое специфическое исключение которое имеет смысл те наиболее глубокое/подробное в иерархии наследования

    • нужно локализовать сообщение на различные языки

    • нельзя раскрывать важную информацию безопасности

  • если у конечного пользователя нет соответствующих полномочий

  • следует сделать отдельные методы(которые может вызвать только доверненный код) для возврата такой информации

  • следует в toString иксепшена ввести код запрашивающий права на выввод секретной информации

    • сообщение должно быть грамматически и синтаксически правильно

  • каждое предложение текста должно заканчиваться точкой.

  • нельзя использовать вопросительные и восклицательные знаки

    • убедитесь что терминология используемая в сообщениях об ошибках имеет смысл в том контексте в котором она употребляется

  • например низкоуровневая терминология имеет смысл только для тех кто работает на нижнем уровне

ТИПЫ ИСКЛЮЧЕНИЙ
1) ошибки программирования, ошибка неправильного использования API (иерархия Exception::RuntimeException)
  • возникает в результате неправильно написанной программы

  • такие ошибки не обрабатывают НАДО перепрограммировать код

  • разработчик должен гарантировать что такая ошибка никогда не произойдет во время выполнения

  • нужно остановить прогу и подробно сообщить юзеру

  • для вызова исключения, можно использовать существующий подтип от RuntimeException:

    1. UnsupportedOperationException вызывается например, при попытке использовать методы записи в readonly коллекциях

      1. например попытка использовать объект до правильной его инициализации

      2. например запись в файловый поток, который был открыт для чтения

      3. напрмиер метод пытается обратится к несуществующему ресурсу

    2. NullPointerException Objects.requireNonNull()

    3. IndexOutOfBoundsException Objects.checkIndex()

    4. IllegalStateException вызываем например, когда значения свойств объекта противоречат друг другу и метод невозможно вызвать

    5. IllegalArgumentException вызываем например, когда юзер передает неправильное значение аргумента в метод

    6. ConcurrentModificationException если объект в одном потоке, обнаруживает что его меняют/изменили из параллельного потока

2) ошибка окружения/environment (иерархия Exception::RuntimeException::MyApplicationException)
  • такая ошибка выполнения может быть обработана программно, например компенсирующей логикой (общая стратегия обработки: attempt recovery)

  • для вызова исключения, рекомендуется создать свое исключение наследуемое от RuntimeException

    1. чтобы отделить иерархию ошибок выполнения от иерархии ошибок программирования RuntimeException

    2. наследовать от Exception не представляется возможным тк исключение сразу станет тогда Checked

  • для имени своего исключения можно использовать суффикс ApplicationException

3) системные отказы (иерархия Error)
  • при системном отказе лучше просто завершить программу

  • в случае внутреннего сбоя лучше, чтобы программа отказала полностью, а не возвращала неправильные результаты, которые только запутывали и усложняли бы ситуацию.

СТРАТЕГИИ РАБОТЫ С ИСКЛЮЧЕНИЕМ В МЕТОДЕ

по умолчанию подразумеваются unchecked/непроверяемые исключения

1) throw concrete UNchecked exception strategy
  • просто генерируем в процедуре иксепшен, иксепшен должен быть необщим

  • стратегия прерывания

  • иксепшен просто пробрасывается автоматически вверх

  • таким образом это поможет централизовать отлов ошибок на более верхнем уровне

2) catch concrete exception / resolve exception strategy
  • стратегия компенсации и возобновления работы

  • те ошибка разрешается прямо здесь

  • ловим только конкретные иксепшены, остальные будут пробрасываться вверх

  • перехватить исключение в точке отказа

  • перехватывать исключение стоит только когда вы понимаете что оно было вызвано в данном контексте и вы знаете как изящно исправить ситуацию

  • перехватывать исключение рекомендуется на более верхнем уровне тк слишком частые исключения на нижнем будут влиять на производительность

  • здесь можно например запустить метод повторно на выполнение через некоторое время и посчитать количество попыток запуска

3) rethrow exception strategy: checked→unchecked
  • обязательное/проверяемое/checked исключение оборачивается в unchecked/непроверяемые исключение

  • иксепшен просто пробрасывается автоматически вверх

  • нужно при повторном вызове исключения, прицепить к нему исходное чтобы непотерялся стек вызовов тк если просто повторно вызвать исключение то оно заместит первоначальное

4) обработчик необработанных ситуаций, завершает работу программы
  • рекомендуется завершать процесс когда код сталкивается с ситуацией в которой дальнейшее выполнение опасно для целостности данных

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

  • следует юзеру показать стандартное сообщение: "Microsoft Word столкнулся с проблемой и должен закрыться. Мы приносим извинения за причиненные неудобства."

  • обработчик необработанных ситуаций используется чтобы восстановить файл и запустить приложение повторно

  • можно также послать письмом сведения об ошибке, позволяющее улучшить продукт

  • сохранить результаты, полученные пользователем и восстановить данные, перезапустить прогу или проигнорировать

АЛЬТЕРНАТИВНЫЕ СТРАТЕГИИ
1) return optional strategy
  • возврат в методе опционального значения, которое в случае ошибки будет пустым

  • но вы НЕ получаете продолжения работы операторов и методов на пустых значениях

  • нельзя делать аргумент optional для того чтобы передать параметр по умолчанию, вместо этого лучше сделать две перезагружаемых функции

  • нельзя использовать Optional того чтобы представить общее значение с тремя состояниями, вместо этого лучше использовать enum

  • в итоге можно использовать цепочку функций (optional api, stream api, completable future, reactive api) с неявными проверками(крытыми внутри библиотек) на пустое значение с (в отличие от явной проверки на null)

2) Try.Success / Try.failure
  • выполнение кода с иксепшеном удобно автоматически завернуть в Try

  • в отличие от Optional, здесь мы во второй ветви(при ошибке) можем получить информацию об ошибке

  • позволяет обрабатывать ошибки как values, а не как поток управления

  • позволяет юзеру выбирать, генерировать исключение или нет

3) Either.left/Either.right
  • когда исключение нам ненужно, те мы сами явно создаем Either и сами его потребляем (например на основании Left пишем лог)

  • когда нам нужна дополнительная информация об ошибке, но мы не хотим расширять Exception

4) Publisher<>
  • можем получить большое удобное разнообразие сигналов

  • можно объеденить потоки в один длинный непрерывный поток

4) return default value strategy
  • в случае ошибки вернем объект пустышку

  • таким образом мы получим продолжение работы программы на пустых значениях

  • такое специально-зарезервированное значение называется "sentinel value"

5) паттерн tester-doer
  • например, метод add (для добавления элемента в коллекцию) вызовет исключение, если коллекция предназначена только для чтения это может быть проблемой производительности в сценариях где метод будет часто терпеть неудачу поэтому, прежде чем попытаться добавить значение, следует проверить коллекцию тестер-методом isReadOnly

  • паттерн улучшит производительность если ТЕСТ стоит намного дешевле чем ДЕЙСТВИЕ с вызовом иксепшена

  • этот паттерн может внести условия гонки в многопоточном вызове метода в случае если класс изначально проектировался с учетом многопоточности(что скорее всего не так)

  • tester -метод используемый для проверки предусловий другого метода, и возвращающий bool (вместо исключения)

  • doer -метод который фактически делает работу

6) паттерн try-parse
  • данный паттерн рекомендуется только если нужно улучшить производительность в низкоуровневом api

  • если у нас есть метод (например Parse) который может вызывать исключение то мы можем сделать альтернативную версию метода (назовем его TryParse) без исключения

  • он будет возвращать true при успехе в кортеже (true,result)

  • Parse - это на самом деле любой метод для которого делаем альтернативный метод

  • TryParse - это альтернативный метод не вызывающий исключения и возвращающий (true|false,result|null)

7) catch all → log →rethrow
  • Иногда требуется залогировать исключение в одном месте и сгенерировать его повторно без всяких изменений, и обработать в другом месте

8) паттерн дорогостоящей проверки потом: те никак не проверяем код
  • если проверка и так НЕЯВНО будет осуществляться при работе объекта внутри далее в коде

  • но тогда возможно понадобится сделать трансляцию исключений rethrow exception strategy: unchecked→unchecked чтобы юзер получил тот иксепшен который он ожидает

НЕ РЕКОМЕНДУЕМЫЕ СТРАТЕГИИ РАБОТЫ С ИСКЛЮЧЕНИЕМ В МЕТОДЕ
1) catch all exceptions strategy: catch(Exception e), catch(RuntimeException e)
  • категорически нельзя "глотать" ошибки перехватывая неопределенные общие исключения

  • в таком случае вы вообще не можете предсказать в каком состоянии находится программная модель.

  • вы рискуете что что польза продолжения работы проги превысит риск обработки недостоверных данных

  • это разрешается только для передачи этого исключения другому потоку те оно в этом случае на самом деле не глотается

2) throw Exception/RuntimeException strategy
  • не следует вызывать общие исключения

  • тк эти исключения являются слишком общими, чтобы предоставить полезную информацию

3) throw concrete checked exception / throws strategy
  • генерируем в процедуре проверяемый/checked иксепшен, должен быть не общим

  • не рекомендуется генерировать проверяемые исключения

  • не рекомендуется делать методы throws

4) rethrow exception strategy: checked→checked
  • не рекомендуется тк при новом исключении вы сообщаете уже о новом отказе, вместо того отказа который произошел фактически

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

  • такое обертывание должно быть редким

  • у этого обертывания, вероятно, будет негативное влияние на возможности отладки

  • практика catch c обертыванием нежелательна и является только еще одной формой проглатывания ошибок

  • это все также частично относится к пункту (3)

5) return null strategy
  • не рекомендуется использовать тк в при попытке использования null объекта вызовется исключение {не в методе а в другом месте}

  • приводит к частым проверкам на null

  • null (как ошибка) может спутаться с null (как с бизнес значением, также называемое "business null")

6) return error
  • не рекомендуется возвращать ошибку или код ошибки в return

  • наличие двух альтернативных механизмов{1) иксепшенов 2)return error} сообщения об ошибках запутывает и ведет к несогласованному применению API

  • исключения в отличие от return невозможно проигнорировать что повышает надежность и устойчивость кода

  • но вы все равно можете в return возвращать информацию о состоянии в случае успешной операции тк юзер может ожидать этого (например число вставленных записей и тп)

  • для исключений созданы специальные инструменты: отладчики, профилировщики

  • исключения не связаны с сигнатурой метода, что дает возможность проектировать возвращаемое значение независимо

интерфейсы

NAMING
  • для интерфейсов предоставляющих возможности имя должно быть прилагательным

    • прилагательное+существительное, но прилагательное является главным словом (так называемое адьюктивное прилагательное)

    • например, c окончаниями, образованными от прилагательных -able и -ible, например Runnable и Accessible

  • класс имплементирующий интерфейс должен использовать суффикс в виде имени интерфейса

    • например Default+имяИнтерфейса для реализации по-умолчанию

    • устаревший суффикс Impl не использую

CONVENTIONS
  • интерфейс делает в точности одну вещь, если у интерфейса несколько функциональных возможностей то это плохо

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

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

    • для каждого интерфейса должен быть минимум один использующий его метод или свойство

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

  • сервисные методы делать статик методами на классе с private конструктором а не на интерфейсе тк методы обработки могут содержать состояние и кеши

    • интрфейсы использовать только для задания типа

РАСШИРЯЕМОСТЬ
  • нельзя требовать чтобы юзеры: наследовали классы или имплементировали интерфейсы

    • где возможно использовать вместо этого лямбды

  • специально созданы для расширяемости через наследование

    • следует выбирать интерфейс если прогеру нужен общий api

  • интерфейсы используются для моделирования множественного наследования

  • для каждого интерфейса нужно сделать минимум одну имплементацию

    • это позволяет проверить правильность дизайна и вовремя исправить ошибки

  • должны сопровождаться полным набором справочной документации описывающей необходимую семантику типов реализующих контракт

    • первоклассная документация должна ясно и обстоятельно объяснить все пред- и пост- условия имплементирования

    • в противном случае пользователи откажутся от использования типа

  • нельзя создавать конструкторы тк нельзя создавать объекты интерефейсов

  • после того как интерфейс разработан, множество его public abstract членов установлено навсегда и не меняется

    • поэтому основная трудность в проектировании правильного набора abstract методов интерфейса

    • для расширения можно добавлять опциональные public default методы

  • количество public методов

    • если у интерфейса слишком много методов то его трудно или даже невозможно имплементировать

    • если у интерфейса слишком мало методов для обещанного функционала, то он становится бесполезным во многих интересных сценариях

ПАТТЕРН SKELETAL IMPLEMENTATION CLASS (ШАБЛОННЫЙ МЕТОД)

  • лучше всего подходят для создания корня иерархии типов {на примере ниже помечен звездочкой}

    • List(*) → AbstractList → MyList

  • паттерн реализуется дефолтными методами на интерфейсе

  • такие имплементации более неполные по сравнению с абстрактным классом

    • тк невозможно переопределить equals, hashCode, toString

    • тк дефолтным методам нельзя переопределять методы Object (но воообще их можно переопределять )

см также ПАТТЕРН SIMPLE IMPLEMENTATION CLASS

  • для каждого интерфейса нужно сделать минимум одну имплементацию

    • это позволяет проверить правильность дизайна и вовремя исправить ошибки

lambda

NAMING
  • добавить к имени функционального интерфейса суффикс

    • Handler

    • Callback

  • добавить к имени метода функционального интерфейса raise

    • не использовать слова fire, trigger

CONVENTIONS
  • вместо лямбд лучше использовать ссылки на функции

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

    • особенно лучше использовать уже имеющиеся функции библиотек

    • если лямбда больше 3-х строк кода то лучше вынести такую лямбду в отдельную функцию и сделать ссылку на нее

  • используйте лямбды вместо анонимных функций

  • параметры метода функционального интерфейса для обработчика события:

    • Object sender - для передачи объекта от кого пришло событие

    • EventArgs e - для передачи аргументов события

    • void - для возвращаемого результата

  • в классе EventArgs рекомендуется сделать параметр события Cancel для отмены события изнутри хендлера

  • рекомендуется уведомлять об изменениях свойств только в высокоуровневых api общих сценариев

    • иначе такие события будут приходить слишком часто если вынести их на более низкий уровень

  • события уведомляющие об изменении свойств: рекомендуется уведомлять только когда свойства меняются извне

  • при вызове события юзеру категорически запрещается

    1. указывать нулевого сендера при отправке события тк подписчики могут его использовать и ожидают что он будет ненулевым

    2. указывать нулевой параметр события, лучше вместо нулевого передавать EventArgs.Empty

  • у лямбд могут быть проблемы с блокировками тк юзерский код лямбды неизвестен

РАСШИРЯЕМОСТЬ
  • нельзя требовать чтобы юзеры: наследовали классы или имплементировали интерфейсы лучше использовать вместо этого лямбды

    • лямбды можно сравнить с расширяемостью через abstract методы

    • лямбды проще для юзеров тк не требуют знания ООП наследования и паттерна шаблонного метода

    • лямбды могут обеспечить расширяемость runtime , а abstract методы имплементируются только во время компиляции

    • лямбды более медленно работают (и занимают в памяти больше метста) чем заранее скомпилированные переопределенные методы

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

    • лямбды обычно передают в параметре метода

  • вместо EventArgs для передачи параметра события рекомендуется пользовательский подкласс

    • тогда потом его всегда можно будет расширить доп свойствами

АРГУМЕНТ МЕТОДА - ЛЯМБДА
  • рекомендуется использовать стандартные типы лямбд, вместо создания пользовательских лямбда-интерфейсов

    • interface Function<T, R> R apply(T t);

    • interface UnaryOperator<T> T apply(T t);

    • interface BiFunction<T, U, R> R apply(T t, U u);

    • interface BinaryOperator<T> T apply(T t, T u);

    • interface Predicate<T> boolean test(T t);

    • interface Consumer<T> void accept(T t);

    • interface Supplier<T> T get();

  • в каких случаях не использовать стандартные типы лямбд:

    • It will be commonly used and could benefit from a descriptive name.

    • It has a strong contract associated with it.

    • It would benefit from custom default methods.

static

CONVENTIONS
  • статик свойства и методы нельзя наследовать

    • нужно сделать такой интерфейс final

    • методы я могу не помечать явно как final тк подразумеваются по-умолчанию