Jekyll2020-08-05T20:57:16+00:00https://roch1990.github.io/feed.xmlMaintainable PythonУлучшаем качество Вашего python кода, делаем devops штуки и наслаждаемся результатом вместе.В доме, который построил Джек2020-08-06T00:00:00+00:002020-08-06T00:00:00+00:00https://roch1990.github.io/post/2020/08/06/v_dome_kotoryi_postoril_jack<p><strong>Предупреждение: Данный пост пропах субъективной оценкой и холиварными суждениями.</strong></p>
<p>Вы часто задумывались про пакеты и их именование? А про именование модулей?</p>
<p>Или может вы все складываете в модуль <code class="language-plaintext highlighter-rouge">utils</code> - “и так сойдет”?</p>
<p>А вишенкой на торте - в один модуль запихнуть с десяток классов, процедур и методов.</p>
<p>Давайте вместе подумаем как сделать лучше, проще и удобней.</p>
<p>Для начала давайте перестать все хранить в utils.</p>
<p>Допустим, у нас есть в utils 3 модуля:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">date_operations.py</code> (как-то преобразовываем даты)</li>
<li><code class="language-plaintext highlighter-rouge">currency_operations.py</code> (преобразовываем валюты)</li>
<li><code class="language-plaintext highlighter-rouge">service_code_converter.py</code> (а тут какие-нибудь системные коды преобразуем в человеко-читаемое нечто)</li>
</ul>
<p>Что можно с ними сделать? Для начала давайте разобъем на гипотетические классы,
с учетом <a href="https://roch1990.github.io/post/2020/07/29/parser_or_new_parser.html">ухода от абстракций к реальным сущностям</a>. Получим:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">DateClass</code></li>
<li><code class="language-plaintext highlighter-rouge">CurrencyClass</code></li>
<li><code class="language-plaintext highlighter-rouge">ServiceCodesClass</code></li>
</ul>
<p>Почему добавляется Class - объясню позже.</p>
<p>Отлично, теперь мы получили набор гипотетических классов, которые надо правильно разложить по пакетам и модулям.</p>
<ul>
<li>для <code class="language-plaintext highlighter-rouge">DateClass</code> формируем пакет <code class="language-plaintext highlighter-rouge">date</code></li>
<li>для <code class="language-plaintext highlighter-rouge">CurrencyClass</code> формируем пакет <code class="language-plaintext highlighter-rouge">currency</code></li>
<li>для <code class="language-plaintext highlighter-rouge">ServiceCodesClass</code> формируем пакет <code class="language-plaintext highlighter-rouge">service_codes</code></li>
</ul>
<p>Теперь, импортируя эти классы, будем получать что-то вроде:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">currency</span> <span class="kn">import</span> <span class="n">CurrencyClass</span>
</code></pre></div></div>
<p>Отлично, осталось разложить по правильным пакетам. Но как это сделать?
Давайте попробуем пакет назвать по названию модуля. Получим что-то вроде:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">currency.currency</span> <span class="kn">import</span> <span class="n">CurrencyClass</span>
</code></pre></div></div>
<p>Выглядит как в песне “В доме, который построил Джек”. В целом - не очень. Но что же делать в таком случае?</p>
<p>А в таком случае - устанвливайте принадлежность данных модулей.</p>
<p>Вот, например, <code class="language-plaintext highlighter-rouge">ServiceCodesClass</code> - скорее всего принадлежит обработке какого-нибудь ответа от смежного сервиса.</p>
<p>А точнее результату его парсинга.</p>
<p>Отлично, тогда можно сделать так:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">message.response.service_codes</span> <span class="kn">import</span> <span class="n">ServiceCodesClass</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">CurrencyClass</code>, впрочем как и <code class="language-plaintext highlighter-rouge">DateClass</code> - скорее всего будут относиться к преобразованию данных в запросах/ответах:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">message.currency</span> <span class="kn">import</span> <span class="n">CurrencyClass</span>
<span class="kn">from</span> <span class="nn">message.date</span> <span class="kn">import</span> <span class="n">DateClass</span>
</code></pre></div></div>
<p>Итого мы получили следующее:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">message.currency</span> <span class="kn">import</span> <span class="n">CurrencyClass</span>
<span class="kn">from</span> <span class="nn">message.date</span> <span class="kn">import</span> <span class="n">DateClass</span>
<span class="kn">from</span> <span class="nn">message.response.service_codes</span> <span class="kn">import</span> <span class="n">ServiceCodesClass</span>
</code></pre></div></div>
<p>И сравним с предыдущим вариантом:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">utils</span> <span class="kn">import</span> <span class="n">date_operations</span><span class="p">,</span> <span class="n">currency_operations</span><span class="p">,</span> <span class="n">service_codes_operations</span>
</code></pre></div></div>
<p>Что же нам это дало?</p>
<p>1) мы теперь четко знаем принадлежность конкретного класса к задаче
2) мы четко видим структуру приложения
3) мы не можем накидать абстрактных методов и процедур в <code class="language-plaintext highlighter-rouge">utils</code>, как в помойку</p>
<p>Итого, новый разработчик, придя на проект - сразу будет понимать 3 основных вещи:</p>
<ul>
<li>Что это?</li>
<li>Зачем это?</li>
<li>Откуда это?</li>
</ul>
<p>Более того, через полгода вам не придется шариться по utils, выискивая ту самую процедуру <code class="language-plaintext highlighter-rouge">get_id</code> или <code class="language-plaintext highlighter-rouge">format_all</code>.</p>
<p>Да, я забыл рассказать почему название класса <code class="language-plaintext highlighter-rouge">DateClass</code>, а не <code class="language-plaintext highlighter-rouge">Date</code>.</p>
<p>По хорошему - вы не должны возвращать примитивы, такие как bool или int. Вы должны возвращать объекты.
В будущем вам будет проще добавить еще одно свойство, чем поменять примитив.</p>
<p>Допустим была у вас функция, которая возвращала True или False. Но тут, вам понадобилось третье состояние!
А давайте быстро заменим на циферки, будет возвращать 0, 1, -1. Почему бы и нет, можем же!</p>
<p>Вы спешно поменяли, сделали рефактор силами idea или сами полазили по коду, нашли все использования этой функции и
поменяли обработчики. Довольные выкатили на какой-нибудь слой для теста ииии…. Все сломалось.
Колесо кармы дало новый оборот.</p>
<p>Если вы будете возвращать объект - вы можете изменить количество его аттрибутов,
сделать наследника класса, формирующий объект, заоверрайдить его методы.</p>
<p>В итоге рядом с <code class="language-plaintext highlighter-rouge">DateClass</code> будет лежать <code class="language-plaintext highlighter-rouge">DateResponse</code>.</p>
<p>Да, это нарушит конракт. Но при этом вы сохраните обратную совместимость. Ну и это не является аксиомой.
Но <strong>именование должно быть унифицировано для всего проекта</strong>. А лучше для всей команды.</p>
<p>Впрочем. это тема для отдельного разговора.</p>
<p>Вот такие мысли сегодня ночью.</p>
<p>Надеюсь подискутируем в этот раз в комментариях.</p>Предупреждение: Данный пост пропах субъективной оценкой и холиварными суждениями.Parser или NewParser - вот в чем вопрос2020-07-29T00:00:00+00:002020-07-29T00:00:00+00:00https://roch1990.github.io/post/2020/07/29/parser_or_new_parser<p><strong>Предупреждение: Данный пост пропах субъективной оценкой и холиварными суждениями.</strong></p>
<p>Давайте поговорим про именование классов!</p>
<p>Делая код-ревью или просматривая чужой код я часто натыкаюсь на всевозможные классы с именами типа Parser, Decoder, Encoder.</p>
<p>Еще хлеще, когда в таком коде множество статических методов.</p>
<p>Как Вы думаете, насколько это валидно?</p>
<p>Лично у меня всегда возникает вопрос - “Почему колесо не называется катало?”</p>
<p>Хочется подискутировать на этот момент.</p>
<p>Вот проектируете вы новый супер-сервис, который парсит входящее откуда-то сообщение, расшифровывает его и складывает в БД, например.</p>
<p>Так, ну тут у нас будет определенно какой-то хендлер, куда сообщение это валится, из которого мы сначала прогоним это сообщение через Decoder, а затем Parser и остаток сложим в БД.</p>
<p>Логично? Ну все, пишем код.</p>
<p>Но не все так просто, как кажется.</p>
<p>Может измениться тип шифрования? Может. Сегодня тебе присылают сообщение в base64, завтра с битовым сдвигом и контрольной суммой.</p>
<p>И что будем делать в таком случае? Писать еще один метод в классе Decoder. Или создадим новый класс, например NewDecoder.</p>
<p>Может измениться структура сообщения? Может. Сегодня тебе шлют XML, завтра Yaml. Что делаем? Конечно же YamlParser.</p>
<p>А еще бывало такое, ну признайтесь себе, когда в какой-нибудь Parser было желание засунуть еще и валидатор и математику какую-нибудь. Бывало ведь?)</p>
<p>В итоге такой класс начинает и парсить, и валидировать, и за кофеём бегать и штаны гладить.</p>
<p>Вот таким образом мы заимеем кучу сущностей, не передающих намерения разработчика, но в которую, как в коробку скидывают всё подряд и засовывают под стол или в дальний шкаф.</p>
<p>Так как же быть? <strong>Связывайте классы с реальными вещами и называйте классы тем, чем они являются.</strong> Parser - не очень реальная сущность, а что-то абстрактное, что умеет парсить сообщение. А сообщение - это конкретный объект.</p>
<p>Валится вам на вход какое-то сообщение - назовите класс Message, задекларируйте методы - decode_base64/encode_base64/parse_to_dict.</p>
<p>Если сообщение XML - сделайте класс XmlMessage, в котором отнаследуетесь от Message и перезагрузите нужные методы. Может и нового добавите.</p>
<p>Если Yaml - YamlMessage.</p>
<p>“Но ведь ничего не изменилось? Мы также плодим кучу классов и сколько хотим методов - столько и пишем!”.</p>
<p>Еще как изменилось. Давайте приведу пример с классом Parser:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">decoded_message</span> <span class="o">=</span> <span class="n">Decoder</span><span class="p">(</span><span class="n">msg</span><span class="p">).</span><span class="n">decode_message</span><span class="p">()</span>
<span class="n">parsed_message</span> <span class="o">=</span> <span class="n">Parser</span><span class="p">(</span><span class="n">msg</span><span class="p">).</span><span class="n">parse_xml</span><span class="p">()</span>
<span class="n">save_to_db</span><span class="p">(</span><span class="n">parsed_message</span><span class="p">)</span>
</code></pre></div></div>
<p>А теперь приведу пример с классом Message:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">message</span> <span class="o">=</span> <span class="n">XmlMessage</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span>
<span class="n">message</span><span class="p">.</span><span class="n">decode</span><span class="p">()</span>
<span class="n">message</span><span class="p">.</span><span class="n">parse</span><span class="p">()</span>
<span class="n">message</span><span class="p">.</span><span class="n">save_to_db</span><span class="p">()</span>
</code></pre></div></div>
<p>Какие различия мы видим?</p>
<p>Во первых это интуитивно-понятное взаимодействие с классом.</p>
<p>Во вторых мы инкапсулируем логику - нам не нужно знать что парсить и как парсить.</p>
<p>А в третьих, это еще и красивей выглядит :)</p>
<p>То есть мы говорим “Колесо - катись. Колесо - остановись.”, вместо “О силы трения качения, используя свою великую мощь - дайте мне катящееся колесо”.</p>
<p><strong>Давайте подискутируем про это.</strong></p>
<p>Может я в корне не прав и надо плодить парсеры с декодерами, а колесо называть каталом?</p>Предупреждение: Данный пост пропах субъективной оценкой и холиварными суждениями.Вам не нужно 100%-е покрытие юнит-тестами2020-07-24T00:00:00+00:002020-07-24T00:00:00+00:00https://roch1990.github.io/post/2020/07/24/you_doesnt_need_100_unit_tests_coverage<p>Сразу оговорюсь, что при прочтении могут возникнуть мысли о капитанстве, наравне с полыханием пятой точки.</p>
<p>Извините уж за это, но я не могу удержаться.</p>
<p>Я часто встречаю позицию такого рода - “Нужно сделать покрытие 100% юнит-тестами, тогда и заживем”. Нет, не заживете.</p>
<p>А почему? А потому что скорость изменения бизнеса не будет вам позволять покрыть код 100% юнит-тестами, если это не замороженный проект.</p>
<p>Даже если вы соберетесь командой N человек и одним геройским рывком покроете функционал юнит-тестами, то где гарантия, что близжайшая бизнес задача не сделает негодными часть тестов?</p>
<p>И что в итоге? Вы снова собираетесь командой N-человек и правите юнит-тесты.</p>
<p>Также, наверняка 100% тестов не получится делать, из-за того, что вы будете уходить “в насыщение”.</p>
<p>То есть вы будете бесконечно приближаться к числу 100, но не будете достигать его.</p>
<p>98%, 99%, 99.9%… Всегда останется строчка кода, которую не покрыть юнит-тестами.</p>
<p>Ну и человеческий фактор. Юнит-тесты пишутся людьми, как и код. И никто не застрахован от “подгонов тестов”.</p>
<p>Возникает закономерный вопрос - “Как же быть?”.</p>
<p>А быть просто - <strong>не покрывайте код 100% юнит-тестами</strong>.</p>
<p>Установите планку, например 80%</p>
<p>“Но тогда часть функционала будет непроверена!” - скажете вы. Да, юнит-тестами она покрыта не будет.</p>
<p>Но! Но <strong>есть еще множество замечательных инструментов, позволяющих установить метрики качества кода, проверить его</strong>.</p>
<p>Давайте по порядку:</p>
<h3 id="pre-commit-hooks">pre-commit hooks</h3>
<p>Прекрасная вещь, которая <strong>проверяет ваш код перед совершением git-коммита</strong>.</p>
<p>И в случае несоответствия его каким-то правилам - отклонит коммит.</p>
<p>А может даже и поправит ваш код. Мощь этого инструмента воистину поражает.</p>
<p>Тут и проверка на pep8 (которым грешат многие разработчики любого уровня, кстати), проверка всяких yamlов-tomlов и т.д.</p>
<p>А самое главное, вы можете создавать свои скрипты, для проверки кода.</p>
<p>Например я, относительно недавно написал набор скриптов, которые проверяют python код на соответствие парадигме элегантных объектов (https://github.com/roch1990/peon).</p>
<p>Подробней узнать о инструменте вы можете тут - https://pre-commit.com/ .</p>
<h3 id="mutual-тестирование">Mutual тестирование</h3>
<p><strong>Производится изменение вашего кода с последующей проверкой</strong> на “изменилось ли что-то в выполнении кода или нет”.</p>
<p>Например у вас есть вычисление <code class="language-plaintext highlighter-rouge">return 2 + 2</code>. Соответственно вы ожидаете 4 где-то далее в вашем ПО.</p>
<p>Тут заходит злобный Mutual-демон и меняет этот кусок кода на <code class="language-plaintext highlighter-rouge">return 2-2</code>.</p>
<p>Все, выполнение программы ломается. Или нет?</p>
<p>Если не сломается, то <strong>возможно что-то у вас в коде сделано не так</strong>.</p>
<p>Из минусов такого тестирования могу лишь указать на затраты на производительность - мне удалось положить гитлаб раннер запускам этих тестов.</p>
<p>Проверить это вы можете (не ухайдакивание раннера :), например воспользуясь библиотекой mutmut для python3 (https://pypi.org/project/mutmut/)</p>
<h3 id="интеграционное-тестирование">Интеграционное тестирование</h3>
<p>Казалось бы - “Да ладно, мы все знаем про интеграционное тестирование!”. Но все ли его делают? И делают ли правильно?</p>
<p>В интеграционном тестировании вы не должны проверять просто тыкая метод палочкой “Ой, отправлю запрос, что-то прилетело в ответ - отлично”.</p>
<p><strong>Постарайтесь охватить большинство бизнес-процессов</strong>. Верные ответы, ошибки. Пусть у вас не будет весь возможный перебор входных параметров запросов (вдруг у вас есть какие-то опциональные).</p>
<p><strong>Не стремайтесь писать отдельное ПО</strong> для проведения интеграционного тестирования, вместо написания отдельных модулей, чтобы они содержались в вашем ПО “из-коробки”.</p>
<p>Пусть это будет bash-скрипт/perl-скрипт, хоть ПО на С++. Главное, чтобы проверка производилась (ну и не вставали волосы дыбом у команды от выбранной реализации).</p>
<h3 id="security-тесты">Security тесты</h3>
<p>Это очень редкий зверь в современном мире, что очень зря. <strong>Сколько сейчас новостей о утечках информации из-за обычных простых ошибок в коде</strong>.</p>
<p>Посмотрите любую ленту новостей, за ближайшие неделю-месяц вы обязательно найдете такую новость.</p>
<p>Чтобы избежать этого - добрые люди придумали <strong>программные пакеты, которые проверяют ваш код на common security issues</strong>.</p>
<p>Удобно, не правда ли? <strong>Добавляете одну строчку в вашу сборку и смотрите отчет</strong>, никаких лишних телодвижений.</p>
<p>И в будущем у вас будет меньше проблем с безопасностью.</p>
<p>Проверить свой код на python можно, используя библиотеку <a href="https://pypi.org/project/bandit/">bandit</a>.</p>
<h3 id="статический-анализ">Статический анализ</h3>
<p>В современном мире это уже чуть ли не обязательный инструмент, который нужно использовать для вашего кода.</p>
<p>И <strong>технический долг посчитает, и надежность кода, и укажет на уязвимости</strong>.</p>
<p>Вообщем выбираете себе подходящий инструмент (благо их сейчас вагон и маленькая тележка, например <a href="https://www.sonarqube.org/">sonarqube</a> и вперед за приключениями.</p>
<p>Таким образом, кроме одного этапа тестирования, у вас будет, как минимум 5.</p>
<p>Пусть <strong>это не покроет весь функционал</strong> (останется какая-то гадость, наверняка), но <strong>вероятность достать белый шарик из коробки, с 5 попыток гораздо выше, чем с 1й попытки</strong>.</p>
<p>В данной статье я целенаправленно не затронул такие вещи как:</p>
<ul>
<li>performance testing</li>
<li>ci/cd</li>
<li>документация</li>
<li>мониторинг</li>
<li>code review</li>
</ul>
<p>Хотя они тоже привносят свой посильный вклад в качество кода.</p>
<p>Но это тема для отдельного разговора.</p>
<p>Данная статья не претендует на научность (пока), но её цель - чтобы хотя-бы у одного разработчика что-то зашевелилось в голове и он решил попробовать разнообразить проверки своего кода.</p>
<p>Если тема понравилась и интересно почитать что-то подобное еще, либо раскрыть что-то поподробнее - пишите комментарии, ставьте лайк.</p>
<p>Всем мир и огнетушителей. Я не тролль :)</p>
<p>P.S.: Ах да, забыл привести вам моё <a href="https://youtu.be/99mU1gSLbkU">вдохновение</a>.</p>Сразу оговорюсь, что при прочтении могут возникнуть мысли о капитанстве, наравне с полыханием пятой точки.