ch
Feedback

不要被骗子欺骗!Telemetrio 会找到并标记这些频道 👉 如果想查看标记,请订阅

DMdev talks

DMdev talks

前往频道在 Telegram

Авторский канал Дениса Матвеенко, создателя DMdev - обучение Java программированию То, что все ищут по Java: https://taplink.cc/denis.dmdev P.S. Когда не программирую - я бегаю: https://t.me/dmdev_pro_run

显示更多
3 252
订阅者
无数据24 小时
+37 天
-1230 天
帖子存档
Kонструктивная и деструктивная критика в Code Review Если простыми словами, то критика - это указание на недостатки (реже достоинства) чего-то, что сделал другой человек. Она бывает конструктивной и деструктивной. Конструктивная критика не причиняет вреда человеку. Она лишь аккуратно указывает на ошибки и через их исправление подталкивает к улучшению и развитию. Конструктивная критика объективна, аргументированна и уважительна. Деструктивная критика, наоборот, бьет по самооценке. Она обвиняет человека и нападает на него. Деструктивная критика необъективна, не аргументирована и зачастую звучит унизительно. Критику мы наблюдаем во всех сферах жизнедеятельности человека, особенно деструктивную в комментариях под моими видео 😁 Но где чаще всего мы ее встречаем в программировании? Правильно - это Code Review, где мы оцениваем код другого человека и оставляем свои комменатрии. И конечно же, нужно использовать только конструктивную критику во время Code Review. Другими словами говоря, важно вести себя вежливо и уважительно, а также быть предельно ясным и полезным для разработчика, код которого ты просматриваешь. Один из способов сделать это - всегда комментируете ТОЛЬКО код и никогда не комментировать разработчика. Например: ❌Плохо:
Зачем ты использовал здесь многопоточность, когда очевидно, что от параллелизма нет никакой пользы?
✅Хорошо:
Параллелизм здесь усложняет систему без какого-либо реального выигрыша в производительности. Поскольку никакого выигрыша в производительности нет, я бы сделал этот код однопоточным. Так его и читать, и тестировать, и поддерживать гораздо проще.
Хорошие комментарии помогают разработчику понять, почему ты их оставил (содержат объяснение), а также довольно часто направляют в сторону принятия более правильного решения (как в данном случае - использовать однопоточный код). А это как раз-таки и есть конструктивная критика 🙂 #dmdev_best_practices

photo content

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

User user = User.builder()
     .firstname("Ivan")
     .lastname("Ivanov")
     .age(35)
     .build();
На примере представлено классическое использование этого паттерна: - объект builder короткоживующий (можно сразу его забыть, потому что даже не пришлось создавать переменную для его хранения) - используется лаконичная цепочка вызовов методов (fluent API) - установка всех параметров самодокументируется и сложно спутать один с другим Когда следует использовать паттерн Builder? В большинстве случаев для замены статических фабричных методов или конструкторов.

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

User user = new User("Ivan", "Ivanov", 35);
Ибо эти варианты подходят только когда класс очень простой и выражение получается компактным. Но в других ситациях это становится неприятным: - очень часто параметры могут быть optional или иметь дефолтные значения - параметры имеют свойство разрастаться - легко попутать похожие параметры одного и того же типа, как на примере firstname, lastname Более подробную информацию можно найти также в книге Effective Java (Item 2)

photo content

Открываю набор на менторство DMdev Кто хочет в Новом Году наконец-то покорить Java и заложить крепкий фундамент на будущее в IT? Стать конкурентоспособным разработчиком на этапе собеседований и/или внутри своей компании? В связи с этим, приглашаю на менторство DMdev первой и второй ступени начинающих и практикующих джавистов!
Что внутри? - недельные спринты с уроками и домашним заданием - два раза в неделю живые созвоны с опытным ментором-разработчиком (на второй ступени со мной) - еженедельное code review - безлимитное общение в чате с ментором
Результат первой ступени менторства - готовый веб-проект в CV, написанный чисто на Java Core. Результат второй ступени - веб-проект в CV, написанный на Java с использованием фреймворков (Hibernate, Spring) ❕Проекты не шаблонные, можно выбрать по своему желанию, чего душа желает❕ Старт: 22 января Продолжительность: 3,5 месяца Количество участников: до 12 человек Оставить заявку и узнать подробности можно по ссылкам ниже: - первая ступень менторства - вторая ступень менторства P.S. Это может стать одним из лучших подарков для себя 🎁 🎄

Избегай 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

photo content

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

@Test
void processMessage_failed()

@Test
void processMessage_missingMessageId_throwsValidationException()
Лишь взглянув на второй вариант, мне будет достаточно понять его суть. Поэтому я нашел для себя этот паттерн именования тестов наилучшим:
apiUnderTest_behavior_expectedResult
#dmdev_best_practices

photo content

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(optionalOptional.get());
} else {
  doOtherWork();
}

// Это можно заменить на конструкцию .ifPresentOrElse, которого очень не хватало в Java 8, и которое добавили только в следующей Java 9:
optionalObject.ifPresentOrElse(this::doWork, this::doOtherWork);
4️⃣ Очень похоже на пункт 2, только объем работы нужно выполнить больше, если значение в Optional существует, и порой приходится даже сохранять это значение в отдельную переменную. Это выглядит так:

Optional<Object> optionalObject = getOptionalObject();
if (optionalThing.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);

photo content

В которой раз убедился и практическим путем доказал даже на примере сегодняшних лайков - человек просто невероятно ленив 1️⃣ Если нет лайков под постом, то процент того, что человек поставит под ним свой - меньше, ибо нужно совершить больше действий (целых два клика! вместо одного) 2️⃣ Если не попросить реакции/комментариев, то их будет просто в разы меньше Кстати, второй пункт работает со всем. Например, на YouTube, думаю, вы часто замечали, что автор просит поставить лайк/комментарий/поделиться видео и т.д. Все потому - что это действительно работает. В своих видео я так не делал ни разу по многим причинам. Поэтому и лайков/комментариев тоже меньше, чем могло бы быть. Поэтому знайте, даже если вас не просят реакций - автору очень приятно их видеть, и он очень благодарен за это. Любая обратная связь невероятно важна человеку! Думаю, это малая цена за создаваемый полезный контент 🙂

Ну вот как после такого не замотивироваться! Можете же порадовать старика 😅 Все, пошел писать следующий best practice на зав
Ну вот как после такого не замотивироваться! Можете же порадовать старика 😅 Все, пошел писать следующий best practice на завтра 💬

Создавай короткие 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

photo content

Создавай короткие lambda выражения Когда ты используешь Stream API или Optional с его приятными методами map(), filter(), etc
Создавай короткие 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. Но гораздо приятнее видеть такое: .f
latMap(fileDescriptor -> readFileAsByteStream(fileDescriptor.getId()))

Зде
сь мне каждая деталь помогает понять логику - и название fileDescriptor - и что я буду считывать файл на основании id из fileDescriptor - и даже результат этого чтения: стрим байт P.S. Заметил, что с каждым постом реакций все меньше. Дай огня, если хочешь продолжение этого адвента. #dmdev_best_practices

Когда избегать Stream API Если комплексный императивный цикл for сложно преобразовать в Stream (особенно если алгоритм этого
Когда избегать Stream API Если комплексный императивный цикл for сложно преобразовать в Stream (особенно если алгоритм этого цикла делает кучу “side effects”) - ничего страшного, продолжай использовать императивный стиль. Я много раз замечал, что программисты впадают в две крайности - либо пытаются все представить в виде стримов, либо не используют их вовсе. Но как обычно это и бывает - правда где-то по середине. Другими словами говоря,
сила в балансе между двумя подходами и умении их комбинировать. Какие-то задачи лучше решить через Stream API, какие-то в императивном стиле.
Кстати, что заметил очень полезного, когда начал использовать стримы - это прокачивание навыка декомпозировать задачи на более мелкие составляющие, что в последующем повлияло на декомпозицию задач любой сложности и любого уровня. Ибо в противном случае не получится компактно и понятно представить логику в виде стримов. Также на своем опыте скажу, что API более высокого уровня я практически всегда определяю с помощью стримов, и только уходя вглубь - я могу прибегать к императивному стилю, если потребуется. Все потому что это очень сильно облегчает чтение программного кода, ибо я начинаю изучение всего “сверху вниз”.

Когда использовать Stream API Императивный цикл for конечно же намного мощнее, чем обычная цепочка вызовов в Stream API. Ты м
Когда использовать Stream API Императивный цикл for конечно же намного мощнее, чем обычная цепочка вызовов в Stream API. Ты можешь: - вернуться из цикла любой вложенности в любой момент времени - изменять данные как угодно и где угодно (а не только affectively final) - создавать несколько выходных результатов из цикла (а не только один) - гибко обрабатывать и пробрасывать исключения - использовать if, else и как угодно еще изменять ход выполнения программы В свою очередь Stream API навязывает множество ограничений на то, что ты можешь делать: - ты можешь определить только последовательный список шагов без использования сложных структур данных и операций ветвлений. Такие ограничения означают, что практически невозможно написать сложный алгоритм, используя Stream API. Либо этот алгоритм будет выглядить очень громоздко, что его проще будет переписать на императивный цикл for. С другой стороны, благодаря таким ограничениям более простые алгоритмы читаются намного лучше и приятнее программистами. Если программист знаком со Stream API - то по такому коду невероятно быстро схатываешь его суть, что он делает.
Поэтому правила просты:
- если задача может быть представлена в виде последовательной цепочки шагов, то обязательно используй Stream API
P.S. А вот как представить многие задачи в виде последовательной цепочки шагов - еще надо научиться! #dmdev_best_practices

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

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

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