08.08.2022

Сергей Немчинский: Как решать задачи как программист?

Сергей Немчинский
10 минут просмотра
Сергей Немчинский: Как решать задачи как программист?

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

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

Этап 1. Анализ задачи

Первое, что вы должны сделать после получения задания — начать его анализировать. Все ли детали задания вам понятны? Очень часто на наших курсах вижу ситуацию, что как только студент получает задание, он сразу хватается за консоль и что-то педалит. Могу с уверенностью сказать, что вы делаете не то, что нужно. Прочтите текст задания минимум 5 раз. Уверяю, что на 3-4 раз прочтения вы заметите те нюансы, на которые сразу не обратили внимание. Возможно, вы выясните, что какой-то кусок задания вообще не описан. Чаще всего задание пишет не программист, а человек на стороне бизнеса. Естественно, он мыслит не как программист, поэтому он не знает, какие аспекты вам нужно описывать, чтобы вы могли выполнить задачу.

Этап 2. Уточнение задачи

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

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

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

Этап 3. Анализ текущего состояния

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

Этап 4. Декомпозиция задачи

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

Этап 5. Построение архитектуры решения саб-задачи

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

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

Я всегда советую, чтобы программисты умели пользоваться UML,  но в данном случае это не обязательно. Потому что в данном случае вам нужно не столько нарисовать конкретные классы, сколько потоки данных. Как вы будете их рисовать — будет зависеть от вас. Можно пользоваться стандартными диаграммами, но можно использовать и просто какую-то кастомную хрень. Главное — делайте это.

Этап 6. Реализация

Когда вы подготовили архитектуру, понимаете, что делать, учли все аспекты, ваша задача понятна, разделена на части и так далее, тогда вы и садитесь за написание кода. Только на шестом этапе, а не на первом, как делают некоторые новички. Главное отличие опытного разработчика от новичкового как раз и заключается в том, через какое время после получения задачи программист садится писать код. Если садится сразу — это прям совсем зеленый джун. Опытный разработчик сначала долго вчитывается в текст задачи, потом начинает ползать по коду, потом что-то рисует, потом, возможно, еще раз что-то уточняет у заказчика. И только потом, когда он уже все понял, садится и быстренько все пишет.

Что делать, если у вас не получается решить задачу — я расскажу позже.

Этап 7. Тестирование

Вам может только казаться, что вы реализовали тот функционал, который нужно. Написанный код нужно запустить и сделать хотя бы smoke testing, проверить прохождение основного позитивного и основного негативного сценариев. Это позволяет проверить, что все в вашем коде работает так как задумано.

Этап 8. Рефакторинг

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

Этап 9. Повторение предыдущих пунктов для всех подзадач

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

Этап 10. Общее тестирование всего функционала задачи

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

Этап 11. Завершение задачи

На этом этапе вы сдаете готовую задачу.

Если задача не решается

Бывает такое, что вы бьетесь об задачу, но ничего не выходит. В этом случае поделюсь своим личным алгоритмом, хотя его придерживаются +/- все опытные разработчики. Алгоритм таков.

1. Понять, можно ли решить проблему гуглением

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

2. Если информация не гуглится — спрашивать

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

3. Если решение можно найти в интернете — гуглим

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

4. Вертите проблему в руках

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

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

5. Сила слабых связей

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

Всегда ваш Сергей Немчинский.