bondiano

О подходе к решению задач

Задачи - первое, с чем сталкиваешься, когда начинаешь программировать по-настоящему. Тут как раз и понимаешь, что программирование далеко не линейный процесс, и нередко таск в Redmine, Jira, Youtrack (или где там у вас записаны задачи), кажется ужасным крестом, который вам предстоит нести ближайшие, если повезет, несколько дней.

Статья навеяна недавним митапом у нас в компании, а также замечательной книгой «Программирование без дураков»

Почти каждая задача, может быть поставлена различными способами, и условия, как окажется в итоге, простой задачи, могут скрываться под тоннами лишней информации и сложными языковыми конструкциями. То, как описана задача может быть результатом сложных идей заказчика, желанием менеджера рассказать побольше (или где-то сократить). При постановки задачи самому себе, к примеру для домашнего проекта, также легко уйти в дебри своих мыслей, и при этом забыть, каков должен быть планируемый результат. Потому для себя я, еще со школьной скамьи, понял, что первым делом, увидив текст задачи, мне требуется определить, что же от меня хотят в конце-то концов (а определив, лучше это куда-нибудь записать). Если возможно, то на этом этапе стоит уточнить максимальное число деталей, а также поговорить о требованиях к задаче с тем, кто их составлял. Иногда, уже на эту стадию уходит огромное количество времени.

Так как статья в основном о задачах в программировании, хороший подход, сразу отнести её к какой-то конкретной категории.

Например, можно категоризировать их так (примерно так это вижу я):

  1. Новый функционал
  2. Улучшение существующего
  3. Ошибки допущенные мной или кем-то (в ходе 1 или 2 пункта)
  4. Баг платформы/инструмента

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

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

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

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

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

Если есть возможность спросить про подозрительный кусок кода у тех, кто уже с ним работал, обязательно стоит воспользоваться этим.

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

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

Реализация задач первой и второй категории отличаются от проекта к проекту. Я всегда начинаю со знакомства с задачей, и не прекращаю изучать цели и требования, пока не осознаю их и задача не становится мне не просто “вроде понятной”, а “хорошим знакомым” (сваливаю на недостаток опыта, то, что у меня часто всё ещё остаются пробелы в целостном понимании задачи). Затем я ищу в задаче то, что мне уже знакомо, и возможно я это уже делал в другом виде.

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

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

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

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