Чему следует научиться большинству молодых программистов.

student-techЗа последние 7,5 лет работы в Ronimo я контролировал работу более десятка программистов-­интернов и просмотрел сотни портфолио студентов и выпускников. В большинстве случаев я замечал одни и те же вещи, которым все они должны были научиться. Кто-­то может подумать, что я говорю об изучении особых приемов, алгоритмов или математики, или о любых других формах конкретных знаний. И, конечно же, это правда, но на мой взгляд никогда не играет первостепенной роли. Главная вещь, которой им следует научиться ­ это самодисциплинированность. Речь идет о той дисциплине, которая заставляет вас каждый раз писать максимально понятный код, проводить рефакторинг кода, если он становится слишком громоздким в ходе последующего процесса разработки, удалять неиспользуемый код и оставлять комментарии.

Большая часть времени, которое я трачу на контроль программистов­-интернов, уходит именно на эти моменты. Не на объяснение продвинутых технологий или деталей работы нашего движка, а на то, чтобы научить их писать более качественный код. Я всегда спрашиваю кандидатов: что, по их мнению, важно для того, чтобы стать хорошим программистом, и обычно они отвечают, что код должен быть чистым, понятным и легко поддерживаемым. Именно это мне и хочется услышать, но на практике очень редко можно встретить молодого программиста, который бы придерживался этих правил.

Чтобы не забывать об этом, нужно иметь внутреннюю дисциплину, потому что она заставляет нас не останавливаться, когда «всё работает». Даже если все переменные будут иметь неправильные названия, код не потеряет своей функциональности, но разобраться в нем будет очень сложно. В краткосрочной перспективе переход от функционального кода к интуитивно понятному не дает ощутимых преимуществ: всё уже работало, и после «наведения порядка» всё по-­прежнему работает. Именно поэтому для того, чтобы сделать этот шаг, нужно быть дисциплинированным. По той же причине студентам полезна интернатура: хороший куратор внимательно следит за качеством кода (несмотря на то, что определение «хорошего кода» у каждого программиста свое), и всегда заставляет интерна или младшего специалиста делать этот следующий шаг.

Позвольте привести вам несколько примеров тех недостатков, которые я часто встречаю в коде начинающих программистов:

Обманчивые функции/переменные/классы

Такие функции, классы или переменные выполняют не ту задачу, которую подразумевает их имя. Их имя ­ это обман. Вполне очевидно, что имена должны быть корректными, но, к моему удивлению, очень часто они совершенно не связаны с назначением субъекта.

В качество одного из примеров можно привести два класса, которые я недавно обнаружил в коде, написанном бывшим интерном: EditorGUI и EditorObjectCreatorGUI. Этот код отвечает за интерфейсы в наших редакторах. К моему удивлению, выяснилось, что код, отвечающий за кнопку для создания новых объектов, находился в классе EditorGUI («графический пользовательский интерфейс редактора» ­ прим. пер.), в то время как класс EditorObjectCreatorGUI («ГПИ модуля создания объектов редактора» ­ прим. пер.) обрабатывал лишь навигацию по различным объектам. Функционал, прямо противоположный имени класса! И даже несмотря на то, что код был довольно простым, мне потребовалось довольно много времени, чтобы его понять, только потому, что я подошел к нему с абсолютно неверным предположением, основанным на именах классов. В данном случае решение было бы очень простым: переименование класса EditorObjectCreatorGUI в EditorObjectNavigationGUI («ГПИ навигации по объектам редактора») сразу сделало бы его намного более понятным.

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

Запутанные классы

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

В качестве примера того, как такое можно произойти, можно представить ситуацию, когда по какой­-либо причине классу GUI («ГИП») необходимо анализировать наличие доступных текстур (например, из-­за существования кнопки для выбора текстуры). Если класс GUI ­ единственный класс, получающий результаты данного анализа, тогда имеет смысл поместить этот функционал внутри класса. Однако, позже какой­-то совершенно посторонний геймплейный класс по какой-­либо причине тоже запрашивает эту информацию. Поэтому вы передает класс GUI этому геймплейному классу, чтобы запросить информацию о текстурах. На этом этапе класс GUI уже вырос во что­-то большее: теперь это и класс TextureAnalyser («анализатор текстур»). Решить эту проблему очень легко: необходимо выделить отдельный класс TextureAnalyser, который может быть использован и классом GUI, и геймплейным классом.

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

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

Слишком большие классы

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

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

Здесь, в Ronimo, мы, как правило, стараемся не создавать классов длиннее 500 строк и функций длиннее 50 строк. Иногда это невозможно или нецелесообразно, но в целом, если класс или функция начинает разрастаться, мы ищем способы для рефакторинга или их разделения на меньшие, более контролируемые фрагменты. (В связи с этим у меня возникает вопрос: где для вас проходит эта граница? Поделитесь своим мнением в комментариях!)

Закомментированный код

Почти все примеры кода, которые присылают нам кандидаты, содержат фрагменты закомментированного кода, без указания причин его комментирования. Это неисправный код, который необходимо доработать? неисправный код, который необходимо доработать? Старый код, который был заменен другим? Для чего здесь этот код? При этом чаще всего кандидаты осознают, что закомментированный код, может создавать путаницу, но почему­-то всё равно оставляют его.

Параллельная логика и дублирование кода

Еще одна часто встречающаяся проблема -­ это наличие схожей логики в нескольких местах. Предположим, что имя текстуры передает некоторую информацию о ее назначении, например, “TreeBackground.dds” («фон с текстурой дерева» ­ прим. пер.). Чтобы понять, можно ли использовать текстуру на объекте-дереве, мы проверяем имя файла ­ начинается ли оно со слова «Tree» («дерево»). Допустим, наш инструмент разработки позволяет очень быстро выполнить такую проверку, благодаря команде filename.beginsWith(”Tree”). Этот код настолько короткий, что если нам нужно использовать его в нескольких местах, достаточно просто его скопировать. Конечно же, это типичное дублирование кода, и все мы знаем, что его следует избегать, но что, если дублируемый код настолько короткий, что так и просится быть скопированным? В этом случае мы сталкиваемся с очевидной проблемой: возможно, в будущем мы изменим механизм проверки соответствия текстуры для дерева. Придется использовать первобытные методы и править каждую копию вручную.

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

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

Автор статьи: Joost van Dongen

Источник


Комментарии:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>