Все люди не умеют писать код

в 16:02, , рубрики: code review, python, аудит кода, Блог компании Конференции Олега Бунина (Онтико), Совершенный код, управление разработкой

В преддверии Moscow Python Conf ++ мы поговорили с Никитой Соболевым, CTO компании «Мы делаем сервисы», о глобальной проблеме управления сложностью кода в разрезе развития языков программирования. А также о том, почему тут со временем ситуация становится только хуже. Плюс расспросили, зачем ему потребовалось создавать собственный линтер.

Все люди не умеют писать код - 1

— Расскажи в двух словах о себе и своей работе

Я технический директор «Мы делаем сервисы». Озвучивая название компании, я обычно задаю вопрос: «Как вы думаете, чем мы занимаемся?». По факту мы специализируемся на веб-разработке: frontend и backend для корпоративных клиентов. А еще мы работаем по собственной методологии, которую совершенствуем параллельно с развитием компании — Repeatable Software Development Process (RSDP).

— На Moscow Python Conf ++ ты будешь рассказывать, в том числе, про собственный линтер. Как твоя работа связана с аудитом и управлением сложностью кода?

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

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

Конечно!

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

Во-вторых Python активно развивается. У него появляются новые синтаксические элементы, новые концепции и модули в стандартные библиотеки, которые ломают все, что было до этого.

— Насколько в Python все плохо? Есть ведь и другие активно развивающиеся языки, например, JavaScript, который как раз за это часто критикуют. В JavaScript ситуация лучше?

Нет. Я бы даже сказал, что в плане сложности у Python все достаточно хорошо относительно других языков. В JavaScript действительно все плохо по одной простой причине: в коде проекта JS смешиваются сразу несколько сущностей, которые не относятся к самому языку — сторонние плагины и библиотеки, которые используются для сборки проекта. Например, если использовать Webpack, появляется возможность писать функцию `import()`, которая подгружает модули асинхронно. Получается, что сборщик засовывает какие-то свои внутренности в ваш язык программирования, и в итоге вообще непонятно, что происходит.

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

В Python ситуация намного лучше. Язык развивается достаточно планомерно, и у этого развития есть понятные вехи. В нем нельзя кардинально поменять синтаксис двумя строчками в конфиге. И это все-таки backend, к которому мы привыкли предъявлять более высокие требования, чем к frontend. Однако, по моему мнению, в Python довольно много новых изменений, которые ломают то, что было раньше, принося сомнительную пользу.

— То есть с развитием языка все становится хуже?

Если вспомнить, что появился AsyncIO — по сути второй язык внутри Python — конечно, сложность возросла очень сильно. По факту сейчас есть два абсолютно независимых языка программирования с похожим синтаксисом: Python и Python + AsyncIO. То есть Python как сущность стал сложнее ровно в два раза, потому что у него появилось два отдельных потомка, работающих по разным правилам.

Мнение о том, что это разные языки программирования, не популярно. Однако когда ты просишь противников этого мнения, например, запустить асинхронную функцию из синхронного кода — у них не получается. Библиотеки тоже абсолютно разные. Хочешь использовать синхронную библиотеку для работы с БД — пожалуйста. А хочешь асинхронную — ее нет.

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

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

Конечно. Для таких людей даже специальный язык программирования придумали. Называется Go. Я не шучу. Действительно, целью создания языка Go была попытка вовлечения в написание кода студентов и стажеров Google, которые не могут освоить C++. Python им не подходил по производительности, нужно было что-то иное, и в Google придумали Go. Как оказалось, очень много людей готовы на нем писать, потому что он очень простой. Но какой ценой достигнута эта простота? Нам дают не нормальный язык программирования, а его очень урезанную версию — в нем нет практически никаких сложных концептов by design. Нет дженериков, нет такого понятия, как исключения и т.д. И поклонников такого подхода много.

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

— Какие типовые проблемы есть у чужого кода?

Обычно они разделены на две части.

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

Когда ты поправил синтаксис, начинаешь обращать внимание на семантику, потому что люди пишут концептуально по-разному. К сожалению, нет возможности договориться на этом уровне — нельзя прийти к соглашению, что мы решаем вот такие задачи так, а вот такие — эдак. Невозможно изначально покрыть все кейсы. Этот процесс происходит во время code review непосредственной задачи: когда разработчику объясняют, почему его решение не может быть принято. Если практика code review применяется и ревьюверы хорошие, они отсекают кривые решения и проблем в коде нет. Но обычно мы приходим на аудит туда, где такой процесс не налажен. И проблемы семантики и архитектуры решать намного сложнее, потому что их даже сформулировать и определить для себя порой непросто.

— И как это выглядит на практике?

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

— В чем ты видишь главную причину того, что эти проблемы вообще существуют?

Все люди не умеют писать код.

Это тезис расшифровывается следующим образом: проблема в том, что мы — люди. И нам вообще очень сложно написать что-то структурированное и логичное. А здесь у нас есть еще и два разнотипных получателя. Во-первых, это человек, который будет этот код читать, и во-вторых, это машина, которая должна его исполнять. Код для машины должен создаваться в соответствии с критериями производительности, потреблением памяти и процессорного времени, а код для человека — на основе принципов читаемости, понятности и т.д. Это две противоположные задачи. И человек, который по сути не может полноценно решить даже одну из них, вынужден решать обе противоречивые задачи одновременно.

— Но ведь использование разных паттернов программирования — это по сути и есть инженерный поиск? Неужели это плохо?

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

Я гораздо чаще наблюдаю творческий поиск. Там таких ограничений нет, валидации полученных результатов тоже. Качество — как в современном искусстве — не поддается измерению.

Практически все клиенты, которые обращаются к нам за аудитом, страдают от типичной ситуации: кто-то им что-то сделал, они наняли разработчика, чтобы как-то развить решение, но тот пришел и развел руками: «Я вообще не знаю, что здесь делать, давайте все заново перепишем». Будет ли хорошо, если переписать? Не будет. Когда ты решаешь заново переписать, наступаешь ровно на те же грабли: ты доверяешь задачу другому разработчику, который делает другие ошибки, но в итоге все получается точно так же.

— Нужен какой-то другой подход?

Да. Во время аудита мы стараемся найти причину проблем с кодом: не почему кто-то взял и раздул модуль до состояния, что он скролится с трудом, а почему изначально было принято неправильное решение. И мы пытаемся автоматизировать или максимально упростить принятие правильных решений в рамках заданных ограничений.

Дам небольшой inside к докладу. У всех есть понимание того, что код состоит из строчек — это самая простая сущность, из которой он может состоять. Каждая строчка может быть написана как

x = 1,

а может быть как

x = Math.median(forecast_data) if forecast_data else compute_probability(default_model).

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

В итоге мы не занимаемся тем, что запрещаем делать многие вещи. Потому что управление — это про вменяемые запреты.

— Попадались ли тебе какие-нибудь забавные вещи в чужом коде?

Безусловно. У меня даже есть репозиторий, где я собираю такие примеры кода.

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

Был случай, когда мы видели в коде очень много смешных комментариев. Кто-то жаловался на жизнь, на работу, были и те, кто писал: «Понимаю, что пишу ерунду, но заказчик меня заставляет». Однако заказчики обычно не заставляют тебя писать плохой код. Они просят решить свою задачу, а уж какой код ты там напишешь, им вообще до лампочки.

— Линтеры, code review — не спасают?

У меня два ответа. Да, спасают. Нет, не спасают. Спасает, если четко следовать тем правилам и предписаниям, которые дают тебе прошаренные линтеры (те, кто за тебя делают много черновой работы: проверяют функции на сложность, семантику кода и т.п.). Этот элемент должен быть блокирующим. Нельзя просто иногда запускать линтер, чтобы посмотреть на результат. Если ты провалил эти правила, то ты вообще не должен выпускать код в продакшн.

Но по факту — не спасают. Потому что те проекты, которые это используют, встречаются редко.

Меня, кстати, часто спрашивают: а как это внедрить? И я отвечаю: очень просто, в CI ставишь строчку — проверить мой код — и если она падает, все, ты внедрил. Осталось только все отрефакторить. Благо, сейчас есть автоформатеры и возможность рефакторить код файл за файлом. Следующий вопрос традиционно: как объяснить бизнесу, что это важно?

— Есть ли какой-то общий ответ на этот вопрос?

Для каждого случая ответы разные, поэтому в общем случае сформулировать сложно (вам надо подумать о том, о сем…). Но обычно компании, которые обращаются с этой проблемой, идут именно с технической стороны. Т.е. технари просят нас, как людей, которые умеют и про бизнес поговорить, и про технику понимают, объяснить это бизнесу в их конкретном случае. При такой постановке задачи это очень просто работает. Когда ты приходишь, уже все плохо, и все это понимают. Разговор с бизнесом начинается так: «Вы, наверное, думаете, что ваши программисты сидят и ничего не делают?». И бизнес кивает головой. А ты говоришь, что дело не в этом. Программисты — замечательные ребята, которые пытаются решить ваши проблемы. Но без комплексного подхода к управлению проектом все сваливается в хаос, и это нормально.

И мы предлагаем вместе придумать правила, позволяющие избежать определенных проблем. Считаем стоимость внедрения разных штук, а потом оцениваем реальные (свершившиеся) потери от того, что таких штук пока нет. Например, программисты целый месяц правили баг, которого не существует или который можно найти за 30 секунд, если использовать определенный подход и инструмент. Цифры хорошо убеждают.

— В итоге это административная проблема?

Конечно. Я убежден, что программисты хотят писать хороший код. Но возникают разные препятствия. Кто-то не умеет по причине неопытности. Кто-то потерял мотивацию, потому что всем все равно. Кто-то не знает, что конкретно является хорошим кодом по причине, скажем так, творческих метаний. На кого-то давят — он хочет и может писать, но ему говорят, что это должно быть завтра. А вместо того, чтобы выстраивать партнерские отношения с бизнесом и объяснять, почему завтра этого не будет (или если будет, то потом еще три дня надо будет править), он делает абы как. А такие партнерские отношения интересны и самому бизнесу. Ему же нужно сделать так, чтобы это работало долго и было дешево в обслуживании.

То есть здесь все вопросы решаемы: нет неразрешимых противоречий.

— Есть же code style — PEP 8. Это не помогает быстро понять, что же хорошо?

В плане запятых — помогает. Но что толку, если ты поставишь запятые правильно, а все остальное будет плохо?

— Не хватает какой-то общеизвестной более высокоуровневой штуки?

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

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

Вообще наша недостижимая глобальная задача — автоматизировать code review, чтобы сам Python (если мы говорим про наш случай) знал, как нужно его писать. Это должен быть инструмент, который поставляет не только возможности, но и ограничения для разработчиков.

— Зачем ты вообще разрабатываешь линтер? Нельзя ли использовать (или развивать) существующие?

На самом деле мы так и делаем. Наш линтер по факту является плагином для Flake8. Просто позиционируем его именно как полноценный инструмент, а не просто плагин.

Почему Flake8, а не Pylint? Pylint делает очень много того, чего именно линтер делать не должен. Например, в нем реализовано очень большое количество проверок на тип, хотя типами должен заниматься type checker. Плюс он выдает очень большое количество ошибок, которых на самом деле нет. А еще мне не нравится его документация, и меня пугает его собственная реализация ast. Он сложен в настройке. Давая возможность конфигурации, ты позволяешь людям сделать неправильный выбор. Поэтому у нас задача — сделать инструмент, который нельзя конфигурировать. Чтобы ты его поставил — и все.

— Какие гайды легли в основу этого линтера? Или здесь только твой собственный опыт?

Сейчас в его основе правила, которые мы многие годы формулировали для себя на code review. Какие-то правила портировали из других линтеров: ESLint, Pylint, SonarQube, Credo. Многое взяли из прекрасной работы «CognitiveComplexity». Всегда оглядывались на Кошелек Миллера. Отдельные правила — это мое видение, появившееся после оценки большого количества чужого кода. То есть на данном этапе это «сборная солянка».

— О чем ты будешь рассказывать на Moscow Python Conf ++?

В первую очередь — про управление сложностью. Эта тема близка и понятна всем разработчикам. Мы посмотрим на разные метрики, на способы переносить сложности из самого простого составляющего кода — строчки — на самый сложный — модуль. А потом поговорим о холиварной части, где я изложу свое видение того, как нужно или не нужно писать на Python, и попрошу пользователей проголосовать, что им нравится, а что — нет. Для многих разработчиков ограничения (делать А, но не делать В) — покушение на их творческое пространство, поэтому они очень бурно на это реагируют. И вот как раз здесь можно развязать интересную дискуссию.

— На кого ориентирован доклад?

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


Друзья, спешим напомнить, что до нашей Moscow Python Conf++ осталось меньше месяца. В этом году на ней будет более трех десятков выступлений и целая серия митапов. Финальная программа будет анонсирована на днях, а пока можно ознакомиться с общим списком докладов.

Автор: eyeofhell

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js