Pull to refresh
8
0
Дмитрий @brutfooorcer

User

Send message

Отвечу вам на оба поста.

Уже есть понимание, что надо тестировать - бизнес логику.

Я лишь к тому, что гонка за цифрой в 100% покрытие кода – это путь в никуда.

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

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

Но в общем случае - вашу позицию я понимаю и вполне с ней согласен.

Для некоторых функций у нас похожее реализовано - есть параметризированный тест, аналитики прописывают условия в json формате и подкладывают их в задачу.

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

Если в задаче не пишется псевдокод. Но это уже совсем другая история.

Я с вами полностью согласен. Вроде, все написанное мной, не противоречит этому)

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

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

И теперь, из-за этого, вы решили вообще все покрыть тестами. Противоречие?

Ну, это не так. Мы решили покрывать тестами 100 кода, что бы быть уверенными, что те функции, которые надо тестировать, будут протестированы. А все остальное, получается, идет прицепом. В общем то, это есть в статье.

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

Абсолютно верно. Это значит, что мы имеем либо функцию без зависимостей (что бы их не мокать), либо проверяем ее вместе со всеми зависимостями (что я бы уже не назвал юнит тестом).

Ну и иногда у функции внутренний контракт меняется, когда меняются условия в ветках или добавляются/изменяются ветки.

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

Об этом и статья)

Мне вот интересно, а что вы думаете про людей, которые указывают 4-5 лет коммерческого опыта, и не могут на лайв кодинге найти среднее арифметическое в массиве? Или выбросить исключение? Таких, внезапно, не мало. Люди, которые прекрасно рассказывают про свой опыт, но не могут ответить, чем отличается поиск по списку от поиска по множеству.

Я заинтересован в разработчике. У меня нет цели "загнобить" его на собесе или самоутвердиться за его счет. Но слишком много сейчас людей после курсов/с чатом ГПТ/с зазубренными ответами, которые хорошо говорят про опыт работы, но в действительности не могут ответить на простейшие вопросы. А еще есть люди с огромным опытом и считающие, что разбросанные по коду main функции в качестве тестов это норма, и если функция не использует состояние класса она должна быть статической.

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

С такими вещами поаккуратнее надо. Без должной разминки порваться очень просто. Вроде, рабочий 120, а потом без разминки ложишься под 90 и рвëшь грудные. И полгода даже отжиматься не можешь.

Вообще такие силовые программы рассчитаны на длительную тренировку с длительным отдыхом между подходами. Хз, почему не хватает батареек больше чем на 40 минут. У меня 5х5 только жим минут 40 занимает)

Я даже не знаю, как ответить на такой вопрос.

Во первых, если бы я хотел набить рейтинг, я бы взял более хайповую тему, а не Java NIO, которая, как вы верно подметили, была реализована аж в 1.4 версии.

Во вторых, я систематизировал то, что знаю, и постарался это описать, в т. ч. для себя. Хотя я думаю, не мне одному это будет полезно и интересно.

В третьих, мне нравится писать код и нравится писать статьи. Собственно, это самое важное - захотел, и написал.

А с какой целью вы интересуетесь?

Да, вы правы. Я предполагал, что это очевидно, а под функционалом имел ввиду "и там и там мы читаем файл".

Подправил статью.

Да, действительно. Спасибо, исправил.

Но ведь я не 2 года оптимизировал этот код, а случайно заметил во время дебага и поправил. За пол часа. А что я делал остальное время? А если после этой правки на проде вылез критический баг и костыль в виде двойной авторизации был не просто так? Ну, это все, конечно, в резюме мы не пишем.

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

Такие ожидания от резюме ведут лишь к одному - при написании разработчики учатся писать банальные вещи так, будто спасли мир позавчера.

Меня всегда удивляют советы типа "напишите, что оптимизировали запрос с 20 до 7 секунд". Ну вот я заметил, что в фильтре двойная авторизация идет, убрал лишнюю строку, я могу написать " уменьшил нагрузку на сервис авторизации в 2 раза"? Или " покрыл сервис юнит тестами". Ну это же обычная работа, давайте всю историю задач в резюме писать, там будет много чего и поинтереснее. Зачем оно там надо?

В сваггере есть @ApiResponse, там можно определить тело для конкретного ответа.

Автор, вроде, указал, почему не хочет выносить оное в controllerAdvice. Как я понял, этот ответ является бизнесовой логикой конкретной конечной точки, и пихать оное в обработчик дефолтных ошибок не очень, потому что сам ответ не универсален.

Да и как бы то ни было, sealed само по себе решение спорное и на любителя, тут, скорее всего, не будет никаких "идеальных" примеров даже учебных. Вот вы пишете, что это можно использовать в либах, что б нельзя было переопределять интерфейс, но это значит, что пользователю в принципе не надо знать об этом интерфейсе. А почему тогда он открытый? А куда девается весь наш полиморфизм? Какой смысл в интерфейсе, реализацию которого мы не можем переопределить?

Документирование кода и комментарии - разные вещи.

Документирование - это описание "что" делает код. По моему опыту, ситуаций, когда функция меняет свою функциональность - крайне редки. Это сродни документировании внешних интерфейсов сваггером, только документируется внутренняя кухня.

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

Самодокументируемый код это очень круто. Жаль на практике самодокументируемый код - это свой код. И то, только пока его помнишь.

Не понятно, почему проблемой является 15 описанных багов вместо 5-7. У вас ставятся задачи на поиск определенного количества багов при тестировании? Сомневаюсь. Если вам важно затраченное время - так давайте тестовое на время. А вот это "нашёл больше чем нужно" в данном контексте абсолютно непонятный параметр.

А в чем проблема сделать синхронную проверку логина, и в случае успеха кинуть ивент в очередь и вернуть ответ? Зачем использовать очередь в синхронном взаимодействии?

Кстати, что будет, если общая шина станет недоступна?

Добрый день!

Очень интересно, десяток описанных интерфейсов - это интерфейсы из паттерна "Издатель-подписчик"? Собственно, там все на них построено)

А вообще, back pressure - это лишь одна из возможностей реактора (вообще, реактивных стримов). Главная особенность реактора, по мне - это управление потоками. У статьи не было цели рассказать о каких то преимуществах, потому ничего о "Зачем нам такие предложения" и не написано.

И все же, вот пример (абсолютно выдуманный из головы). У нас есть некий поток объектов. Нам нужно конвертировать его в сообщение, подгрузить два поля для этого сообщения по http, затем обновить его в бд и, наконец, кинуть некое уведомление и создать соответствующий эвент в кафке. Мы можем выполнить это все последовательно, но львиную долю всех этих действий можно выполнять параллельно. и на реакторе такой код будет примерно таким:

Код на реакторе
Flux.fromIterable(List.of())
		.flatMap(this::convert)
		.publishOn(Schedulers.boundedElastic())
		.flatMap(message -> {
			return Mono.zip(
					loadField1(message),
					loadField2(message)
			).flatMap(t -> {
				Object field1 = t.getT1();
				Object field2 = t.getT2();
				message.setf1(field1);
				message.setf2(field2);
				return update(message);
			}).then(Mono.when(
					sendNitification(message),
					createEvent(message)
			));
		});

Опишу некоторые подробности:

  1. Каждое сообщение обрабатывается асинхронно (каждое последующее сообщение не ждет окончания обработки предыдущего).

  2. Два поля подгружаются параллельно (http запросы).

  3. После подгрузки полей выполняется обновление в бд.

  4. После обновления в бд параллельно отправляется уведомление и создается событие.

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

А если еще представить, что объекты приходят к нам, например, из кафки, то тут и открывается полезность back pressure: мы имеем возможность регулировать поток входящих объектов в зависимости от наших ресурсов. Но это уже в теории, на практике использовать back pressure мне не приходилось.

Добрый день!

Что то мне подсказывает, что event loop в реакторе отсутствует, потому я про него ничего и не написал). В статье я описал общий принцип работы, в остальном - в реакторе присутствует куча реализаций Flux и Mono (описывать которые - дело скорее документации, чем статьи), да утилитные классы. Кстати, думал как раз следующим в netty покопаться - вот там event loop, кажется, есть.

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

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

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

Я могу привести другой пример: математические операции.

public enum Operation {
        PLUS,
        MINUS,
        MULTIPLY,
        DIVIDE;
}

Я надеюсь, никто не скажет, что они могут умирать, или может оказаться, что у них появится вторая функция? Так вот, возьмем абстрактный метод int apply(int a, int b, Operation operation). Как бы вы реализовали его - запихнули бы внутри свич кейс, или реализовали бы для каждой операции отдельный класс? А может логичнее в енам добавить поле ToIntBiFunction<Integer, Integer> func, и вызывать его для произведения операции? Енам - это такой же класс, как и любой другой, почему же мы не можем завезти логику для объектов, перечень которых ограничен?

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

Вот кейс: существуют некие "заявки". У них есть 15 полей, среди которых отдельными сущностями сидят: создатель, организация, предоставляющая услугу, исполнитель, статус. Создатель/исполнитель/организация довольно крупные объекты.

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

Так вот, по факту, нам нужно вытянуть лишь саму заявку и ее статус. Статус представляет собой сущности в ограниченном количестве (допустим, 10 различных статусов). Они очень редко меняются, а если кто то и поменял название, то ничего страшного, что пользователь некоторое время увидит старое (и вместо статусов можно взять, например, некий тип заявки, или любое "классификационное значение" - да вообще много можно чего придумать).

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

Information

Rating
Does not participate
Location
Россия
Registered
Activity

Specialization

Backend Developer
SQL
Java
Docker
RabbitMQ
Apache Kafka
Spring Boot
REST
Hibernate
Java Spring Framework
PostgreSQL