Избегайте бесконечной отладки: советы для новичков разработки.
Привет. На данный момент я работаю тех-лидом в компании Binary Studio, а так же последние несколько лет подряд являюсь трек-овнером и ментором направления .NET в Binary Studio Academy.
Эта статья, в первую очередь, будет полезна студентам и начинающим разработчиками, которые уже пробовали писать коммерческие проекты, но еще не успели (и не хотят) набить лишних шишек. Я старался сконцентрироваться исключительно на практических примерах, избегая лишних рассуждений. Весь материал базируется на моем личном опыте, поэтому, если вам есть что дополнить - пишите в комментариях.
Используй инструменты
Если бы я мог вернуться в то время, когда я только начинал свою карьеру, одним из самых главных советов самому себе был бы: "Используй инструменты!". Как сейчас помню: я писал курсовую работу, задача которой была получить изображение с камеры, обработать его набором алгоритмов, записать некоторые данные в базу и отобразить обработанную картинку на экран. И это все в режиме реального времени. Сложнее всего было отлаживать такую программу: нужно было одновременно следить за ошибками по работе с камерой, правильностью алгоритма, обращениями к базе данных и, конечно же, работой с UI фреймворком (кто работал с WPF стилизацией, тот меня поймет). Со временем, набив несколько шишек и поучаствовав в разработке ряда веб-проектов, я выработал для себя некоторый набор принципов, которые могут помочь справиться со сложностью.
Как и любой уважающий себя джун, я брался за решение задачи целиком: для того, чтобы сверстать страницу, мне приходилось создавать веб-приложение в Visual Studio из темплейта, среди списка существующих файлов находить нужную страницу и писать HTML/CSS наугад, так как результат можно увидеть в браузере только после нажатия F5. Если мне нужно было отправить запрос на сервер, то я добавлял кнопку на страницу, вешал на нее обработчик, добавлял логику построения HTTP-запроса, нажимал F5 и только после клика на кнопку я мог посмотреть, правильно ли я описал обработчик запроса на сервере. Visual Studio была моей точкой входа в приложение и точкой выхода. Часто я даже не понимал, что происходит при нажатии на F5, потому что моей целью было решить ту или иную задачу. Знакомо?
Разделяй и властвуй
Являясь коучем Binary Studio Academy, а также проверяя по несколько десятков домашек за сезон, я каждый раз сталкиваюсь с тем, что начинающие разработчики совершают одну и ту же ошибку: они пытаются охватить сложную задачу целиком, вместо того, чтобы разбить ее на мелкие составляющие.
Эта стратегия уменьшения сложности задачи путем ее разделения на более простые подзадачи известна как «Разделяй и властвуй». В этом случае есть два предположения, которые обычно верны:
- Задача может быть разделена на несколько частей, так что каждая часть может быть решена независимо.
- Решение каждой из меньших частей, составляющих задачу, менее сложна, чем решение всей задачи, и, таким образом, мы можем «победить» ее.
Верстка и фронтенд.
Сейчас если мне нужно написать UI, я не запускаю сервер. Вместо этого я открываю онлайн редактор (например, Codepen) и верстаю нужные мне компоненты. Если мне нужно редактировать уже существующую часть приложения, то Chrome позволяет редактировать HTML/CSS на лету, используя вкладку Elements в Chrome DevTools. После этого останется всего лишь скопировать финальную версию и вставить себе в приложение (эта намного проще, чем перезапускать приложение после каждого малейшего изменения). Кроме этого DevTools позволяет отлаживать JS (не алертами, а с помощью переходов), расставлять точки останова, отслеживать переменные и читать Call Stack. Отдельные экстеншены позволяют упростить работу с JS фреймворками (Angular, React), отслеживать состояние приложения (Redux), а также отслеживать производительность.
Коммуникация между клиентом и сервером.
Если мне нужно отправить запрос, то я больше не создаю кнопку Test на форме. Вместо этого есть несколько инструментов, которые позволяют описать HTTP-запросы во всех деталях: Curl, Postman, Fiddler, RestClient. Последний мне нравится больше всего, так как это экстеншн к VS Code и он позволяет описывать все запросы в виде интерактивного текстового файла, которым легко управлять. Если мне нужно более внимательно анализировать сетевой трафик, то можно обратиться к Fiddler или Wireshark. Таким образом я могу сфокусироваться на разработке конкретного API метода в один момент времени, не обращая внимание на остальную часть приложения.
Разработка бизнес-логики.
Если говорить о бизнес-логике приложения, то в первую очередь в голову приходят юнит-тесты. Вместо того, чтобы запускать все приложение целиком, юнит-тесты позволяют сфокусироваться на конкретной функции, которую можно вызвать с теми или иными аргументами. VS предоставляет довольно удобный интерфейс для того, чтобы запускать и отлаживать тесты, а для их написания можно использовать один из следующих фреймворков: xUnit, nUnit, MSTest. Не обязательно добиваться 100% покрытия тестами, достаточно уже того, что вы сможете проверить правильную работу вашего кода без запуска приложения целиком. Я специально ни слова не сказал про дебаг, потому что мне кажется, это первое, с чем сталкиваются начинающие программисты. Но кроме дебага я советую также разобраться с дизассемблером и научиться читать чужой код (пакеты, библиотеки). Это даст вам более широкую картину того, что происходит за ширмой библиотечных вызовов.
Взаимодействие с базой данных.
Не стоит забывать и про данные. ORM всячески стараются скрыть от разработчика работу с SQL. Вы пишете модель, затем пару вызовов библиотечных функций и получаете необходимую выборку. Но чем сложнее система, тем сложнее выглядит эта модель и функций становится уже не одна пара. Поэтому заранее вооружитесь инструментами для работы с базой данных: IDE, профилировщик, анализатор структуры запросов, анализатор логов. Если вы работаете с MS SQL, то большинство инструментов идет из коробки (MS SQL Management Studio, SQL Server Profiler, Execution Plan analyzer), для других баз данных вам придется гуглить каждый инструмент по ключевым словам.
Очень важно, чтобы вы не просто разбили приложение на отдельные составляющие, но и, в тех местах, где вы определили “швы”, описали заглушки - мок-данные, скрипты с HTTP-запросами, SQL-запросы. Это может быть полезно по двум причинам: во-первых, всегда можно было проверить изолированный компонент, во-вторых, при объединении всех частей в единое целое у вас перед глазами был контракт, которому необходимо следовать.
Перед тем как подвести итог, я бы хотел привести пример разработки конкретной фичи (логин пользователя), используя все то, что я перечислил выше.
- В первую очередь я фокусируюсь на UI. Как я уже говорил, я открываю Codepen и стараюсь нарисовать интерфейс компонента, который соответствует мокапам (два поля для ввода логина и пароля и кнопка "Login").
- Когда UI готов, я могу перенести весь код в компонент и создать все необходимые обработчики. На данном этапе я подменяю все сервисы по работе с данным на сервисы с объектами заглушками, для того, чтобы проверить работу кнопки без обращений к серверу.
- После того, как все операции по работе с формой (клик на кнопку, валидация, сообщения об ошибке или переход на другую страницу приложения) правильно работают с объектами заглушками, я могу переключиться на бекенд. Сначала я описываю эндпоинт, который принимает модель и возвращает код 200. Используя RestClient, я описываю HTTP-запрос в текстовом файле (адрес, заголовки и тело запроса) и проверяю, что мой сервер отвечает правильно.
- Перед тем как приступить к бизнес логике, я посмотрю в базу данных и проверю, что все запросы, которые связаны с авторизацией, можно выполнить на существующих данных. Так как обычно я использую ORM, этот шаг является всего лишь подстраховкой, потому что ORM сама за меня построит все запросы, а мне нужно всего лишь правильно описать модель.
- Теперь я могу приступить к бизнес логике. Я не буду вдаваться в подробности реализации функционала авторизации, хочу лишь сказать, что на этом этапе правильность вашего алгоритма легче всего будет проверить с помощью юнит-тестов. Добившись того, что моя функция авторизации работает верно, я могу подключить ее к базе данных и вызвать внутри API метода. И проверить целую связку, используя все тот же текстовый файл с запросом.
- После того, как связка API+логика+база данных работают, я могу подменить на клиенте сервисы с заглушками на реальные запросы к серверу и если все контракты были описаны правильно, то я автоматически получаю рабочий функционал. Стоит отметить, что до последнего шага я каждый раз фокусировался на маленьком участке программы и только в конце я запустил все приложение целиком.
Конечно, такой подход не идеален и имеет свои изъяны. Во-первых, здесь очень важна дисциплина. Всегда хочется взяться за все и сразу. Важно научиться делить большую задачу на несколько составляющих и фокусироваться на каждой из них по отдельности, отбрасывая все остальное на потом.
Во-вторых, баги никуда не денутся. Однако такой подход дает больше контроля на каждом этапе и если, например, вы видите, что ваш сервер возвращает не те данные (пользуясь вкладкой Network в браузере), вы можете переключиться на текстовый файл запросов и не использовать браузер до тех пор, пока сервер не будет работать правильно.
В-третьих, вы могли заметить, что в процессе разработки у меня появилось несколько дополнительных артефактов: mock-сервисы с заглушками, текстовый файл с HTTP-запросами, юнит-тесты и запросы к базе данных (которые тоже желательно сохранять в отдельные файлы). В дальнейшем все эти артефакты вам придется поддерживать. Конечно, вы всегда можете избавиться от всех дополнительных файлов, как только закончите разработку конкретной фичи, но я крайне не рекомендую этого делать. Гораздо лучше постоянно обеспечивать себе подобного рода "леса" для того, чтобы в любой момент времени вам было на что опереться.
Наконец, скорее всего вы будете работать над проектом не одни, поэтому желательно, чтобы вся команда следовала подобному подходу. В противном случае кто-то перестанет поддерживать дополнительные артефакты или полностью удалит их за ненадобностью (см. пункт выше).
Когда я поступал в Binary Studio Academy, то не смог найти подобной статьи, которая бы подтолкнула меня в правильном направлении. Это стоило мне несчетное количество часов безудержной отладки, красных глаз и испорченного настроения. Наблюдая за нынешними студентами, я часто вижу, как они наступают на те же самые грабли: тратят много времени на уже решенные проблемы, вместо того, чтобы сконцентрировать все усилия на предметной области, а второстепенные задачи постараться делегировать инструментам. И даже если описанный выше подход к вам не применим, а написание дополнительного кода в тягость, запомните главное: "Используйте инструменты!"