Найти тему

Плохой код и путь от безысходности

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

Плохим кодом можно назвать вообще много всего. Это зависит и от уровня проекта и от уровня компании и от уровня программистов. В конце концов – если компания в 50 человек захочет велосипед для управления каким-то бизнес-процессом, то идеальный код ничего не поменяет, пусть остается мусорным. Но если есть желание побороться за качество собственной работы – я могу предложить приемы, которые использую в своей работе сам. Да, эти приемы очевидны. Да, все о них знают. Да, я не говорю ничего нового. Только вот мой личный опыт показывает, что многие программисты работают по принципу «сделали и ладно». Ну что же, начну делиться личным опытом.

Первый используемый прием – Unit-тесты. Он же самый трудный, нудный и долгий. И к слову – если в процессе написания тестов - он действительно невыносим. Значит код проекта – мусор. В чем же проблемы?

1) Цикломатическая сложность (вики) – Каждый новый ифчик используемый в методе это +1 тест для 100% покрытия. Если вы написали юнит-тест на белый сценарий работы метода, а он покрыл только 10-20% кода метода, то ваш код мусор.

2) Длина метода (количество строк). Вот мы набили шишки на сложности и написали простой метод в 150 строк. Начали писать unit-тест, и на 10ой настройке мока уже глаз дергается, и кнопки ctrl c v начали стираться. Несмотря на внешнюю простоту – ваш код зависит от результатов работы многих других модулей. Соответственно любой из них может выдать исключение. Так что – ваш код мусор.

3) Использование «окружения». Это опять же тема с моками. Хотим мы этого или нет – но мы используем какие-то глобальные системные сущности. Например - конфигурацию, сессию, сущность пользователя, в контексте которого выполняется метод. Если вы не можете быстро и просто мокировать эти сущности – значит ваш код плох. Фактически он сильно зависит от внешнего окружения, которое вы не особо контролируете.

Каков итог – писать тесты нужно не только для тестирования. Они еще не слабо помогают выявлять плохой код.

Второй используемый прием – использование готовых решений. В качестве примера я использую сборку AutoMapper. Для тех, кто не в курсе – это такая библиотечка, которая с помощью одного вызова метода Map позволяет проецировать объект одного класса в объект другого класса. Я проще задачи не знаю – что сложного вызвать конструктор и передать в него все необходимые параметры? А сложность все-таки есть. Во-первых, в случае изменения структуры сущности – вы будете лазить по всему коду, и вносить правки. Можно написать свой конвертер? Да, можно. А сколько у вас сущностей и сколько нужно конвертаций? Можно написать фабрику? Да, можно. Только проблема та же. Классов, скорее всего, много, и как они будут расти – вы не знаете. И если к этому добавить нежелание писать тесты на произведенную ерунду – шанс огрести проблем с багами растет экспоненциальными темпами. А ведь можно использовать уже готовое. Нужно лишь написать конфигурацию маппинга и вызывать один единственный метод одного интерфейса. Хотя стоит признать, что конфигурацию тоже лучше покрыть тестами. К чему это? Я постоянно натыкаюсь на монструозные вызовы конструкторов. В случае возникновения ошибки – вы никогда не найдете место, в котором она возникла. А ведь есть простая альтернатива. И это касается почти любого действия. Да, каждый проект имеет свою бизнес-логику, но это не значит, что, нужно реализовывать сериализацию/десериализацию XML, управление потоками, взаимодействия с БД и т.д. и т.п. Иногда 30 минут поиска готового решения заменяют целые трудовые недели и годы поддержки собственных решений.

Последний прием, но не по значению, который хочется упомянуть – это пара вопросов, которые нужно задавать самому себе после (а лучше ДО) написания кода.

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

2) «Как будет развиваться данный код в будущем?». Если ответите «никак, он для одной текущей задачи» - я вас расстрою, вы написали мусор. Рано или поздно возникнет необходимость переиспользования. Вы конечно можете понадеяться, что ваш потомок перепишет ваш код, и он будет более универсальным, но уж лучше это сделать самому, чем рисковать потом копипастой коллеги. Мой личный обнаруженный рекорд – 20 методов копипасты в контроллере. 10 заявок от клиента * 2 способа доставки результата. И авторов не волнует сей факт, никому глаз не режет.

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