dd

О разработке, работе руками и сношении с машиной

  • Телеграм-бот для водоканала на DI и тестах

    Сегодня я очень рад и доволен собой!
    Последние 26 часов я делал телеграм-бота для местного водоканала.
    Бот представляет из себя урезанный, но от того не менее полезный, функционал личного кабинета.
    Зная заказчиков, конкретно телеграмом дело не обойдётся и потому было принятно решение пилить независимое решение, чтобы можно было его подогнать под любую платформу, которая этих ботов поддерживает.
    В прошлом я уже делал бота для этой компании, правда там функционал был совсем небольшой: всего лишь передача показаний по счётчикам. Тот бот был написан на коленке и как можно скорее, а потому не имел за собой никаких тестов и перед выдачей проверялся просто руками. Всё это привело к тому, что бот всё-таки работает, но развивать его нереально. В этот раз, естественно, всё делалось через тесты.

    Так как изначально курс был взят на независимость от платформы, то тесты и DI показали себя здесь в полной красе. Я теперь официально в эту тему влюблён.
    Все зависимости от внешних сервисов, базы и тд были вынесены за интерфейсы и через DI вместо них подключались самые простецкие заглушки.
    Таким образом, мне абсолютно ничего не мешало пилить чистый функционал бота и формировать свои требования к формату входных и выходных данных, независимых от конкретного бота.

    В качестве выходных данных были выбраны «сообщение» (обёртка над строкой) и «действия» (простые dto с описанием того что надо будет сделать и каким текстом это показать пользователю).
    Все популярные платформы поддерживают подобные схемы в том или ином виде.
    На приложенном скриншоте показано как это выглядит в итоге в телеграме.

    Кстати, разработки обёртки, которая отдаёт данные в машину и отправляет ответ пользователю, заняла около часа + ещё час ушёл на мелкие доработки, вроде удаления кнопок действия после того как пользователь выбрал одно из действий. В итоге сильно выиграем в дальнейшем при разработке ботов для других платформ.
    Добавление же новых кнопок или состояний будет тоже занимать мало времени: добавляем «действие», его обработчик, пишем небольшой тест и в продакшен.

    В общем, это был очень полезный опыт разработки отдельно от внешней среды. Хоть я и раньше знал как это всё делается, но пощупать в таком масштабе этот приём крайне интересно.


  • Аналогия из «Совершенного кода»

    У Макконелла в «совершенном коде» иногда попадаются крайне интересные аналогии, вот одна из них


  • Деплой по SSH-ключу

    Краткая инструкция по работе с ключами для деплоя, чтобы каждый раз не гуглить:
    1. создаём ключ через ssh-keygen -t rsa
    2. путь указываем любой, желательно чтобы он содержал в себе название репозитория
    3. делаем cat /путь/до/ключа.pub и вывод добавляем в delpoy keys своего git-сервиса (в gitlab это settings -> repository -> deploy keys)
    4. проверяем: ssh -T your-username@your-git-server.com
    5. заставляем git ходить на сервер именно с этим ключом: ssh-agent sh -c 'ssh-add /путь/до/ключа; git pull;'
    6. вы великолепны

    вместо git pull можно подставить любую другую операцию, просто в моём случае репозиторий уже был развёрнут на сервере


  • Что такое ExecutionResult и зачем он нужен

    Когда я начал работать с Yii2, то меня вполне устраивало, что в результате выполнения любого метода на объекте можно вернуть true/false, потому как если какие-то ошибки и были, то их всегда можно было потом вытянуть через getErrors(). Или, того хуже, можно было кинуть исключение, а выше его отловить и использовать сообщение исключения где-нибудь дальше.
    Однако, с течением времени в голову начали закрадываться мысли, что было бы неплохо, если бы результат выполнения метода сам рассказывал как всё прошло, успешно ли и где я там накосячил, а так же возвращал данные, сформированные в ходе выполнения.
    Таким образом родился ExecutionResult — data transfer object для получения данных о результате работы формы. В данном случае под формой подразумевается класс, принимающий и валидирующий входные данные, а затем выполняет над этими данные необходимые операции. Это позволило решить две проблемы:
    1. Результат работы формы теперь содержался в одном объекте, мне не приходится больше в трех строках кода вытягивать те или иные данные после выполнения
    2. Инкапсуляция восторжествовала: форма стала по-настоящему чёрным ящиком — она отдаёт только те данные (ошибки, объекты или сообщение об исключении), которые посчитает нужным
    Конечно, это не панацея и всё должно быть в меру, но, как мне кажется, решение вышло достаточно простым и удобным.
    Собственно, сам интерфейс ExecutionResult:
    ⁃ bool $success
    ⁃ mixed $data
    ⁃ array $errors
    ⁃ string $exception
    В имплементации это всё обёрнуто геттерами и имеет и несколько статических методов для быстрой генерации того или иного варианта.
    Кстати, из-за такой структуры ExecutionResult удобно использовать как ответ на api-запрос.
    P.S. Буду рад услышать какую-либо критику в адрес этого метода, либо запрос на пример использования в конкретной ситуации


  • Почему password_hash работает медленно

    Сегодня через тесты писал систему авторизации в новом проекте. Заметил, что по сравнению с практически любыми другими тестами они выполнялись очень медленно, хотя там работы ноль — сравнить почту и пароль в базе. Даже тесты на создание заказа с тяжёлыми запросами и логикой выполняются в среднем быстрее.
    Полез искать в чём дело и выяснил, что всё упирается в функцию password_hash(). Как оказалось, она намеренно сделана медленной для повышения безопасности. Грустно, конечно, но оптимизировать безопасность, пожалуй, дело не самое лучшее.
    Подробнее тут: https://stackoverflow.com/questions/30215781/why-is-password-hashing-e-g-phps-password-hash-so-slow


  • Первая реакция на доску мыслей

    Написал это в пятницу утром и наконец-то дискуссия началась, правда, почему-то с бухгалтером, которая спросила что это значит, а не с программистами🤔


  • Доска мыслей в офисе

    Решил записывать на доске на кухне офиса различные мысли (свои и не очень) в надежде на зарождение горячей дискуссии; пока что всем всё равно🥲


  • Принцип единственной ответственности на практике

    Впервые услышав о принципе единой ответственности был очень недоволен такой концепцией, потому как в моей голове это означало, что теперь, допустим, на любую операцию с моделью из базы, у которой есть зависимость от другой модели, придётся делать по два отдельных класса, например: класс удаления самой модели и класс удаления связи с зависимой моделью (реализация какая угодно, от формы до репозитория)
    Однако, на деле всё оказалось куда удобнее и интереснее: srp это об единой причине для изменения, то есть в один класс можно запихнуть и удаление самой модели, и различные связанные действия, так как все они относятся к одному событию — удалению модели. После этого меня перестала мучить совесть🙃


  • SSH-подключение без пароля

    краткий гайд о том как подключаться по ssh без пароля (надоело каждый раз лезть в гугл)
    1. ssh-keygen -t rsa
    2. путь указываем такой, чтобы было понятно к какому именно сервера вы подключаетесь
    3. пароль не обязательно вводить
    4. копируем ключи на удалённый сервак: ssh-copy-id -p порт пользователь@айпи
    5. авторизуемся при помощи пароля от учётки, чтобы завершить процедуру
    6. для удобства можно внести в alias'ы консольной оболочки что-нибудь типа myssh="ssh user@ip", чтобы не вводить каждый раз


  • DTO для разделения бэка и фронта

    На своей первой работе по воле судьбы оказался фуллстак-разработчиком, это сильно облегчало работу на ранних стадиях проекта, в котором я участвовал, но не позволяло мне увидеть один интересный и удобный приём работы с данными — DataTransferObject. Мы чаще на фронт просто передавали объекты напрямую и пользовались методами для доступа к данным как попало.
    На текущей же работе, в веб-агентстве, разделение на бэк и фронт максимально явное и передавать фронтендерам объекты из своей области совсем перехотелось; собственно, dto стали ответом на многие вопросы и часто приходят на помощь; тут главное не переборщить, но это уже совсем другая история