DMdev talks
3.24K subscribers
156 photos
13 videos
89 links
Авторский канал Дениса Матвеенко, создателя DMdev - обучение Java программированию

То, что все ищут по Java:
https://taplink.cc/denis.dmdev

P.S. Когда не программирую - я бегаю:
https://t.iss.one/dmdev_pro_run
Download Telegram
🎄 Как насчет ежедневных best practices вплоть до Нового Года?

С завтрашнего дня в рубрике "DMDEV ADVENT CALENDAR"

Новый день = новая возможность сделать твой код чуточку лучше!
Включай уведомления и зови друзей!
🔥16111👍11❤‍🔥3😍2🤯1
Не будь излишне терпим к null

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

Например, когда сравниваешь объекты
object.equals(CONSTANT)
- тебе необходимо учитывать, что object может быть null.
Yoda notation
CONSTANT.equals(object)
- обычно является той самой попыткой быть терпимым к null. Но зачастную такое сложнее читается, ибо выглядят не "естественно".


Поэтому:
- если ты НЕ ожидаешь, что object может быть null, object.equals(CONSTANT) - это отличный вариант написания.

- если ты ожидаешь, что object может быть null, то лучше использовать Objects.equals(object, CONSTANT) - это сделает код более чистым и понятным. Более того, он говорит, что object может принимать null значения.

- для параметров метода, которые не должны быть null, вообще лучше использовать fail-fast принцип Objects.requireNonNull (обычно генерируется автоматически с помощью тех же аннотаций Lombok)

#dmdev_best_practices
🔥85👍18❤‍🔥62👏2
Используй @Nullable в своем коде

Эта аннотация говорит о том, что значение может быть null, и зачастую используется в трех местах: полях класса, параметрах метода или даже возвращаемого значения (для локальных переменных не следует, да и смысла особого нет).

Ее не обязательно использовать в коде, но если начал - то продолжай расставлять ее во всем своем проекте или модуле:
consistency превыше всего!

В противном случае, другие программисты будут считать, что если что-то не аннотировано @Nullable - то оно не может быть null.

Подытожим:
- используй @Nullable везде, если это возможно
- если не можешь поддерживать consistency во всем коде - то лучше избегай @Nullable (или везде, или нигде!)
- если используешь @Nullable, то смысла в @NotNull аннотации нет - ибо все и так по умолчанию будет восприниматься not null

#dmdev_best_practices
🔥60👍20❤‍🔥73😁1
Предпочитай кастомные аннотации, нежели использование @Named

Аннотации @Named и @Qualifier (Spring annotation) - очень мощный инструмент, позволяющий определять и использовать несколько бинов одного и того же типа. Вообще, спецификация JSR 330 Dependency Inject предоставляет два варианта: использование стандартных аннотаций вроде @Named, либо создавать свои кастомные. И именно второй вариант должен быть предпочтительным.

Основной недостаток стандартных аннотаций - это использование строкового значения в качестве идентификатора бина @Qualifier("primaryHttpClient")

Это неудобно и довольно легко допустить ошибку в написании, тем более что эти строки проверяются не во время компиляции, а только в момент уже создания бина (runtime). А если бин lazy - то это как бомба замедленного действия.

Конечно, существуют всякие статические анализаторы и подсказки от мощных IDEA, но они минимальны и не всегда помогают. Поэтому именно кастомная аннотация обеспечивает все необходимые проверки и помогает избегать типичных опечаток:


@Documented
@Retention(RUNTIME)
@Qualifier
@interface PrimaryHttpClient {}


#dmdev_best_practices
👍52🔥219❤‍🔥5💯1
Когда использовать Stream API

Императивный цикл for конечно же намного мощнее, чем обычная цепочка вызовов в Stream API.

Ты можешь:
- вернуться из цикла любой вложенности в любой момент времени
- изменять данные как угодно и где угодно (а не только affectively final)
- создавать несколько выходных результатов из цикла (а не только один)
- гибко обрабатывать и пробрасывать исключения
- использовать if, else и как угодно еще изменять ход выполнения программы

В свою очередь Stream API навязывает множество ограничений на то, что ты можешь делать:
- ты можешь определить только последовательный список шагов без использования сложных структур данных и операций ветвлений.

Такие ограничения означают, что практически невозможно написать сложный алгоритм, используя Stream API. Либо этот алгоритм будет выглядить очень громоздко, что его проще будет переписать на императивный цикл for.

С другой стороны, благодаря таким ограничениям более простые алгоритмы читаются намного лучше и приятнее программистами.

Если программист знаком со Stream API - то по такому коду невероятно быстро схатываешь его суть, что он делает.

Поэтому правила просты:

- если задача может быть представлена в виде последовательной цепочки шагов, то обязательно используй Stream API


P.S. А вот как представить многие задачи в виде последовательной цепочки шагов - еще надо научиться!

#dmdev_best_practices
❤‍🔥46👍33🔥242
Когда избегать Stream API

Если комплексный императивный цикл for сложно преобразовать в Stream (особенно если алгоритм этого цикла делает кучу “side effects”) - ничего страшного, продолжай использовать императивный стиль.

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

Но как обычно это и бывает - правда где-то по середине. Другими словами говоря,
сила в балансе между двумя подходами и умении их комбинировать. Какие-то задачи лучше решить через Stream API, какие-то в императивном стиле.


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

Также на своем опыте скажу, что API более высокого уровня я практически всегда определяю с помощью стримов, и только уходя вглубь - я могу прибегать к императивному стилю, если потребуется. Все потому что это очень сильно облегчает чтение программного кода, ибо я начинаю изучение всего “сверху вниз”.

#dmdev_best_practices
👍61🔥468❤‍🔥1
Создавай короткие lambda выражения
Когда ты используешь Stream API или Optional с его приятными методами map(), filter(), etc - избегай длинных и сложных lambda выражений в них.
Их не только становится сложно читать, но такой подход убивает всю суть стримов: их компактность и быстрое понимание общей логики кода. Благодаря компактности у программиста появляется возможность смотреть на код с высоты птичьего полета. А если нужны более подробные детали - то уже опускаешься глубже в каждый конкретный метод map(), filter(), etc.

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

Например, вряд ли кому-то понравится код, который выглядит примерно так:

.flatMap(
it -> {
try {
return readFile(it.getId()).stream();
} catch (IOException e) {
return Stream.empty();
}
})


Здесь не все так плохо еще, потому что хотя бы вынесена логика по чтению файла в отдельный метод readFile.
Но гораздо приятнее видеть такое:

.flatMap(fileDescriptor -> readFileAsByteStream(fileDescriptor.getId()))


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

P.S. Заметил, что с каждым постом реакций все меньше.
Дай огня, если хочешь продолжение этого адвента.

#dmdev_best_practices
🔥366👍2710💯4❤‍🔥2🙏2
Ну вот как после такого не замотивироваться! Можете же порадовать старика 😅

Все, пошел писать следующий best practice на завтра 💬
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7822🔥14🤩7😁6❤‍🔥1
В которой раз убедился и практическим путем доказал даже на примере сегодняшних лайков - человек просто невероятно ленив

1️⃣ Если нет лайков под постом, то процент того, что человек поставит под ним свой - меньше, ибо нужно совершить больше действий (целых два клика! вместо одного)
2️⃣ Если не попросить реакции/комментариев, то их будет просто в разы меньше

Кстати, второй пункт работает со всем. Например, на YouTube, думаю, вы часто замечали, что автор просит поставить лайк/комментарий/поделиться видео и т.д. Все потому - что это действительно работает.
В своих видео я так не делал ни разу по многим причинам. Поэтому и лайков/комментариев тоже меньше, чем могло бы быть.

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

Думаю, это малая цена за создаваемый полезный контент 🙂
142👍65🔥21💯14❤‍🔥7🤔3🤩3🙏2🥰1👏1😱1
👍19🎉42
Optional.isPresent(), .isEmpty(), and .get() методы обычно не следует использовать

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

1️⃣ Представим такую "классическую" логическую конструкцию if-else, где нужно вернуть значение:

if (optionalObject.isPresent()) {
return doWork(optionalObject.get());
} else {
return "other";
}

// Она может легко быть представлена двумя другими в зависимости от надобности в "ленивой" инициализации значения в else:
return optionalObject.map(this::doWork)
.orElse("other");

return optionalObject.map(this::doWork)
.orElseGet(this::doOtherLazy);


2️⃣ Порой нам надо сделать что-то со значением, если оно существует, и это действие void (в отличие от пункта 1). А если его нет - то ничего не делать. И такая конструкция:

if (optionalObject.isPresent()) {
doWork(optionalObject.get());
}

// Заменяется на:
optionalObject.ifPresent(this::doWork);


3️⃣ Похоже на пункт 2, но нам нужно сделать else действие, которое тоже void, если значение в Optional не существует.

if (optionalObject.isPresent()) {
doWork(optionalObject.get());
} else {
doOtherWork();
}

// Это можно заменить на конструкцию .ifPresentOrElse, которого очень не хватало в Java 8, и которое добавили только в следующей Java 9:
optionalObject.ifPresentOrElse(this::doWork, this::doOtherWork);


4️⃣ Очень похоже на пункт 2, только объем работы нужно выполнить больше, если значение в Optional существует, и порой приходится даже сохранять это значение в отдельную переменную. Это выглядит так:

Optional<Object> optionalObject = getOptionalObject();
if (optionalObject.isEmpty()) {
return;
}
Object object = optionalObject.get();
doWork(object);
// code ...
doWorkAgain(object);

// Вместо этого мы можешь просто воспользоваться .orElse(null), чтобы сразу получить значение из Optional и далее работать только с ним:
Object object = getOptionalObject().orElse(null);
if (object == null) {
return;
}
doWork(object);
// code ...
doWorkAgain(object);


#dmdev_best_practices
🔥198👍39❤‍🔥9🤯2😍21👏1
👍13🔥3
Названия тестов - очень важны!

Название теста должно описывать поведение (test case). Когда программист просто читает название теста - этого уже должно быть достаточно, чтобы понять, что тест пытается проверить и какой ожидаемый результат.

Другими словами говоря, название теста служит для своего рода самодокументации.

На своем при
мере:
Когда я хочу посмотреть возможные варианты использования какого-то API, то при хорошо написанных тестах - мне достаточно просто взглянуть на них. Хорошие тестовые имена очень здорово описывают поведение API при самых разных условиях/входных параметрах, которые ты, порой, даже не ожидал. Ибо у того, кто писал этот API, как правило было больше контекста, чем у тебя, и он больше понимал его логику и его нюансы.

Для интереса можно сравнить два примера одного и того же test case:

@Test
void processMessage_failed()

@Test
void processMessage_missingMessageId_throwsValidationException()


Лишь взглянув на второй вариант, мне будет достаточно понять его суть.
Поэтому я нашел для себя этот паттерн именования тестов наилучшим:
apiUnderTest_behavior_expectedResult


#dmdev_best_practices
🔥108👍385❤‍🔥5
👍8🔥2
Избегай boolean параметров

Если это возможно, то лучше избегать boolean параметров в public методах (открытый API). Все потому, что такие структуры очень плохо и читаются в коде, и пишутся, да и тестировать тоже неприятно. А создавать целые переменные только для того, чтобы описывать true/false флаг, программисты попросту не будут (да и не поможет это).

Например, глядя на этот код, можно только гадать, что значит true, и что будет, если в метод передать false:

Result result = processMessage(message, true);

Как можно этого избежать?

1️⃣ Джошуа Блох в своей книге Effective Java (Item 51) советует создавать enum с двумя значениями. Что и легко читается, легко документируется, и в случае чего - можно добавлять третье/четвертое значение и т.д.

Result result = processMessage(message, ProcessingType.DRY_RUN);


2️⃣ Заменить один метод на два других с более подходящими названиями.

Result result = processMessage(message);

Result result = processMessageDryRun(message);


3️⃣ Воспользоваться техникой уменьшения количества параметров в методах, т.е. просто создать объект, который будет содержать все параметры:

ProcessOptions options = ProcessOptions.builder().message(message).dryRun(true).build();

Result result = process(options);


4️⃣ Если по каким-то стечениям обстоятельств не помогли три предыдущие пункта, то нужно хотя бы оставить комментарий, что означает boolean параметр:

Result result = processMessage(message, /* dryRun= */ true);


#dmdev_best_practices
👍108🔥50❤‍🔥6
Открываю набор на менторство DMdev

Кто хочет в Новом Году наконец-то покорить Java и заложить крепкий фундамент на будущее в IT?

Стать конкурентоспособным разработчиком на этапе собеседований и/или внутри своей компании?

В связи с этим, приглашаю на менторство DMdev первой и второй ступени начинающих и практикующих джавистов!

Что внутри?

- недельные спринты с уроками и домашним заданием
- два раза в неделю живые созвоны с опытным ментором-разработчиком (на второй ступени со мной)
- еженедельное code review
- безлимитное общение в чате с ментором


Результат первой ступени менторства - готовый веб-проект в CV, написанный чисто на Java Core.
Результат второй ступени - веб-проект в CV, написанный на Java с использованием фреймворков (Hibernate, Spring)

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

Старт: 22 января
Продолжительность: 3,5 месяца
Количество участников: до 12 человек

Оставить заявку и узнать подробности можно по ссылкам ниже:
-
первая ступень менторства
-
вторая ступень менторства

P.S. Это может стать одним из лучших подарков для себя 🎁 🎄
Please open Telegram to view this post
VIEW IN TELEGRAM
👍33🔥16❤‍🔥3
Используй Builders

Builders - это короткоживущие mutable объекты, которые имеют одну единственную задачу: собрать все необходимые данные, чтобы создать другой объект, который в свою очередь обычно immutable и долгоживующий.


User user = User.builder()
.firstname("Ivan")
.lastname("Ivanov")
.age(35)
.build();

На примере представлено классическое использование этого паттерна:
- объект builder короткоживующий (можно сразу его забыть, потому что даже не пришлось создавать переменную для его хранения)
- используется лаконичная цепочка вызовов методов (fluent API)
- установка всех параметров самодокументируется и сложно спутать один с другим

Когда следует использовать паттерн Buil
der?
В большинстве случаев для замены статических фабричных методов или конструкторов.


User user = User.of("Ivan", "Ivanov", 35);

User user = new User("Ivan", "Ivanov", 35);

Ибо эти варианты подходят только когда класс очень простой и выражение получается компактным. Но в других ситациях это становится неприятным:
- очень часто параметры могут быть optional или иметь дефолтные значения
- параметры имеют свойство разрастаться
- легко попутать похожие параметры одного и того же типа, как на примере firstname, lastname

Более подробную информацию можно найти также в книге Effective Java (Item 2)

#dmdev_best_practices
👍82🔥35❤‍🔥6