June 17, 2021

Когда total чуть больше чем всё

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

да, я знаю что коряво, но это временно, а потом переделаем

Что будет завтра - я не знаю, а то что я сделаю сейчас, останется как минимум до того момента когда это будут переделывать. Ещё раз, что бы не сложилось впечатление что я считаю себя лучше всех. Нет, это не так. Я могу писать плохой код, я могу отмазываться что это конкретное костыльное решение мы потом переделаем, я могу совершать ошибки. Но есть некоторые вещи которые меня просто адски триггерят. Особенно если влезают в мой код. Хм... Представляю что обо мне думаю другие, когда я влезаю в их код.

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

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

Пока над проектом работал только я то и претензий не было. Ну кроме как ко мне. Сам накодил, сам и исправляй. Когда подключился другой разработчки... Блин, прости меня. Мне правда очень жаль из-за того что поставил тебя перед фактом что "а теперь мы делаем по-другому". Это реально свинство и я стараюсь так не делать. Но в своё оправдание могу сказать что я тут же провёл для тебя подробный ликбез о том что именно поменялось, зачем и почему это лучше. А ты был молодец. Ты принимаешь "правила игры" и мы вместе контрибьютим в общий проект. И вот теперь подключается третий разработчки...

Для меня этот проект - как ребёнок. Я переживаю каким он будет. Я не хочу что бы он передвигался на костылях и ходил в заплатках. И я понимаю что не могу всю свою жизнь положить на него. У меня есть другие "дети", а у проекта есть другие "родители". И я предполагаю что свои изменения они тоже вносят только исходя из лучших соображений. Но хватит сухого языка, разбавим скукоту живым кодом.

Была задача - внешний клиент дёргает метод сервиса и получает список транзакций, удовлетворящий критерию поиска. Ответ подразумевает постраничность. Человек сказал что подумал, но как можно было додуматься до такого?

public function handle(ServerRequestInterface $request): ResponseInterface { $parsedBody = (array) $request->getParsedBody(); $pagination = $parsedBody['pagination'] ?? []; $criteria = $parsedBody['criteria'] ?? []; $txSearchVo = TransactionSearchValueObject::create($criteria); $paginationVo = PaginationValueObject::create($pagination); if ($paginationVo->getTotal() === 0) { $total = $this->searchService->searchTransactions($txSearchVo, null, true); $totalCount = 0; foreach ($total as $count) { $totalCount += $count['total']; } $paginationVo = $paginationVo->withTotal($totalCount); } else { $paginationVo = $paginationVo->withTotal($pagination['total']); } $transactions = $this->searchService->searchTransactions($txSearchVo, $paginationVo); return new JsonResponse( [ 'criteria' => $criteria, 'transactions' => iterator_to_array($transactions), 'pagination' => $paginationVo->toArray(), ] ); }

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

Почему, узнав что в параметрах запроса не был указан total, мы ищем транзакции и почему второй аргумент null, а третий true? Ладно, согласен, юродствую. Из названия переменной можно предположить (и предположение оказалось верным) что если total не указан в запросе то нужно сначала найти сколько всего транзакций. Допустим.

Но повторюсь. Результат - плоский список транзакций. Тогда почему вот тут

foreach ($total as $count) {
$totalCount += $count['total'];
}

оказывается что total это массив, который нужно просуммировать? Такое ощущение что транзакции сгруппированы и мы ищему их общую сумму.

Дальше не легче. Оказывается что если вызвать тот же самый метод, но с другими параметрами, то получим уже не total, а транзакции.

$transactions = $this->searchService->searchTransactions($txSearchVo, $paginationVo);

Почему параметров тут два? Ок, то есть при первом выполнении мы не использовали объект с пагинацией. Ну теперь можно вернуться на несколько строчек выше и понять что null был заместо (привет, Сергей) пагинации. Мне же нечего делать кроме как вверх и вниз по коду бегать. Ну и ещё опусы как это писать. Но почему тут исчез третий параметр? Ах да, там он был true, значит если его опустить то можно получить список транзакций, а не список их количества. Написал это предложение и аж физическую боль почувствовал.

Ну и что бы удостовериться идём и смотрим как именно объявлена эта чудесная функция для поиска и транзакций и их количества.

public function searchTransactions(
    TransactionSearchValueObject $txSearchVo,
    ?PaginationValueObject $pagination = null,
    bool $countResults = false
): Traversable {
    return $this->manager->searchTransactions($txSearchVo, $pagination, $countResults);
}

То что там ещё глубже показывать не буду. Там не так уж что бы и плохо, просто грязно. Но я не удивлён... Но зато теперь пусть только попробует кто-то сказать что в PHP не бывает перегрузки методов.

И сразу отступление. Я говорил что в проект очень сомнительное архитектурное решение. Почему-то решили что разделив сервисы, менеджеры и репозитории код получится... Хз, если честно, что хотели получить. Но в итоге сейчас очень большая часть выглядит как тупое проксирование вызовов от метода handle контроллера до репозитория. Получается что если хочешь посмотреть как работает запрос то сначала ищешь URL и контроллер который его обрабатывает. Тут притензий нет, нашёл котроллер - тебе нужен его метод handle. Дальше в контроллере видишь что инджектится сервис и вызывается метод сервиса. Ок, кликаешь на этот метод, проваливаешься в сервис и... И видишь что метод сервиса просто вызывает метод менеджера, который инжектился в сервис. Ок... Кликаешь на метод и оказываешься в менеджере. Прикол в том что в огромном числе случаев реализация метода менеджера - вызов точно такого же метода репозитория, инджектнутого в менеджер. То же название, те же аргументы, тот же ответ. Всё то же самое, но что бы добраться до основной локиги от хендлера приходится покликать. Повезёт если ты сразу знаешь какой сервис нужен. Или кому-то не нужно будет использовать репозиторий. Тогда может доберёшься до цели за два перехода. Но назад к коду.

Резюмирую, понимаем что получаем запрос, в котором может быть есть total. Если нет то посчитаем количество транзакций. Если есть то принимаем решение не считать total и просто возвращаем переданное значение. Другими словами, клиент, получивший наше API в какой-то момент заметит что можно выполнить запрос, передав total = 1 и при этом ответ будет получен быстрее. Но количество записей уже на первой странице, которую он запросил будет больше 1. Или можно получать страницу за страницей без total, но работать будет чуть дольше.

То есть total это и total и флаг. Причём может быть любым, но если будет ноль то запрос будет работать дольше и в ответе будет другой total. Или не будет.

Профессор Плейшнер увидел на окне 17 утюгов и понял что явка провалена.

Из этой же серии ☝️

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

Ещё раз. Много раз ещё раз. Я отдаю отчёт в том что нельзя просто так взять и всех спасти. То что я щас написал это вообще моё личное имхо. Тем более я же понял что делает код, ну так чего выпендриваться. Но ужасно бесит что некоторые просто не хотят видеть чуть дальше своей клавиатуры. И я, наверное не исключение.