7 причин говнокода в Unity

Демотивирующая картинка

Мне неоднократно приходилось наниматься в проекты где приходилось поддерживать унаследованный код. Качество таких проектов обычно ниже всякого плинтуса. Хорошего, годного кода в проектах на Unity вообще мало. Крупный геймдев и корпорации, конечно, стараются поддерживать планку code quality высоко, но и там случаются факапы. Типичный Unity-проект, сделанный разработчиками на уровне мидла, если посмотреть на него объективно, на самом деле чудовищен. «Под капотом» в таком приложении обычно «винигрет» из макаронного кода и неестественных решений для обхода архитектуры Unity. Сверху это приправлено костылями, поддерживающими неестественные решения и посыпано щедрой пригоршней плагинов. Последние пытаются конфликтовать друг с другом, и вокруг этого бывает построена отдельная подсистема, по очереди выключающая плагины так, чтобы они давали работать другим. Архитектура такого проекта построена по принципу «горстки риса» — в смысле, «ну, так сложилось». Ядро проекта представляет собой монолитный копролит, и становится legacy (тем самым которое «работает – не трогай») уже через 2-3 месяца. Рефакторить и поддерживать такой проект без консультации с автором (хотя бы для того чтобы найти точки входа) другой разработчик не способен. В терминальных случаях не способен и сам автор.

Вся эта ситуация приводит к трудностям при разработке, постоянным срывам сроков, дрянному качеству конечного продукта, нервным срывам у программистов и потерям денег у бизнеса. Тем не менее, это продолжается, и ситуация пока что усугубляется с каждым годом. Цитируя советского сатирика, «мы ж должны шо-то здесь решать?!?!»

Причины некачественного кода в Unity

Проанализировав свой опыт, мнения коллег и те проекты, с которыми мне приходилось работать, я выделил главнейшие причины, порождающие говнокод в Unity и отравляющие жизнь разработчикам. Я расставил их по степени их влияния на процесс разработки и разработчиков.

1. Низкий порог входа

Это наиболее очевидная и банальная причина, ставшая особенно актуальной в последние пару лет. Unity в целом — достаточно дружелюбная к начинающему разработчику среда. У него простой и понятный интерфейс, в комплекте идёт подробная документация, в интернете есть множество обучающих статей и видео, а так же множество бесплатных ассетов и плагинов, облегчающих старт и разработку. Всё это позволяет любому школьнику, имеющему достаточно мотивации и свободного времени, садиться и прямо сразу начинать писать «свой собственный MMORPG СТАЛКИР для Андроед». Ну или Roblox – он щас популярнее.

Проблема в том, что такой школьник имеет меньше понимания о программировании, чем среднестатистический кот. Более того, чаще всего это не отличник, а бестолковый хикки-троечник, мотивированный не писать хороший код, а «стать инди-разработчиком, делать свои игры и рубить бабло как %username%» (подставьте имя известного инди-разработчика). Его мотивация – не «быть программистом», а «заниматься играми» — потому что в его жизни компьютерные игры это главнейшее развлечение и зачастую единственная отдушина, и ему интересна именно бесконечная возня в Unity как в большой песочнице.

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

2. Неправильное/недостаточное понимание принципов работы Unity

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

Программирование под Unity по своей сути ближе к программированию для промышленных роботов, автопилотов и дронов, чем к generic programming. Логика работы всего, привязанная к постоянно повторяющемуся рабочему циклу Update() (фактически, «soft real time») и принципам поведения объектов в сложном окружении, требует вывиха мозга в правильном направлении.

Многим довольно трудно понять и принять это и другие особенности Unity, особенно в том случае если есть опыт разработки приложений других типов. Беда в том, что очень многие обучающие материалы, распространённые в сети, затрагивают базис Unity довольно слабо, фокусируясь на решении частных проблем. С одной стороны это понятно, т.к., для опытных юнитистов это нечто само собой разумеющееся… Но практика показывает что очень важно донести это до начинающего разработчика, чтобы он не пытался «костылями» бороться с тем что нормально для Unity и составляет основные принципы его работы.

3. Трудность отладки

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

Представьте себе, например, написанную на Unity RTS, где у вас по полю боя бегает под сотню юнитов. Их поведение в том или ином виде описывается кодом. Вы запускаете игру, смотрите на них, и замечаете, что с течением времени – не сразу, но через десяток минут – они начинают вести себя не так как планировалось. Где-то накапливается ошибка. Но где? У вас на сцене несколько сотен взаимодействующих объектов. По отдельности, будучи выпущенными на на отдельную отладочную сцену, юниты ведут себя правильно – вы сами это видели, многократно наблюдая за поведением вашего условного космодесантника в процессе разработки. Каждый юнит управляется несколькими параметрами, генерируемыми игровым ИИ на основе ещё нескольких входных параметров, и взаимодействует с другими юнитами и окружающим виртуальным миром ещё через несколько параметров. Где-то на промежуточном участке что-то вносит какое-то воздействие, генерирующее ошибку непонятного генезиса. Глазом это видно, а вот источник ошибки в коде не очевиден. Как говорится, «счастливой отладки».

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

4. Недостаточное знание инструментария

Очевидно, что Unity – это не С образца 1989 года, в котором для разработки хватало знания 32 команд, текстового редактора, прямых рук, горячего сердца и компилятора объёмом 72 килобайта.

Как и любая современная среда разработки Unity предоставляет множество инструментов для создания и отладки приложений. Собственно, сам смысл названия Unity – «Единство» — отражает суть ситуации. В базовую поставку Unity включён фактически весь возможный инструментарий, который может потребоваться разработчику игр – вплоть до редакторов анимаций, ML-решений, нарезки спрайтов, средств воспроизведения видео, реализаций AR и VR, и так далее. Разумеется, не весь этот инструментарий идеально хорош. Некоторые решения не оптимальные, некоторые на первый взгляд выглядят переусложнёнными. Но важно то что все эти инструменты унифицированы и очень качественно подогнаны к тому способу работы, который можно было бы назвать Unity way. Под этим термином я, по аналогии с “python way”, подразумеваю «наиболее изящный способ, полно использующий возможности системы и принятый для использования в данной среде разработки».
В руководствах и официальных туториалах Unity весьма подробно рассказано о том, как правильно применять эти инструменты. Однако некоторым разработчикам лень долго копаться в мануалах. И начинается импровизация.

Инструменты Unity не используются, вместо них применяются платные плагины и собственные костыльные решения. И добро если это платные плагины, которые хотя бы оттестированы. Но часто в коде встречаются и «велосипеды», решающие средствами кода то, что можно было бы решить встроенными механизмами Unity, без кода. Наиболее типичный пример – анимация из кода, когда пишется самодельный механизм, передвигающий объекты по сцене согласно заданным математическим функциям. Зачем так делать? У вас Animator есть!

5. Непонимание методик разработки и принципов программирования

Это наиболее типичная причина появления говнокода, распространённая не только в Unity-разработке, но и в IT вообще. Я говорю о том самом непонимании принципов ООП, паттернов программирования, отсутствии юнит-тестов, практик экстремального программирования и прочего. Это весьма банально, много раз обсуждалось и навязло в зубах… Но всё так же продолжает создавать грабли, на которые разработчики наступают.

А грабли эти, к сожалению, весьма опасны. Некачественный код, написанный по этой причине, создаёт гораздо более значимые проблемы, чем анимация из C#-скрипта или макаронный код джуна. Это происходит потому что такого рода код порождается более опытными программистами (от хай мидла и выше), занимающимися архитектурой приложения, и от него зависит много другого кода. Неправильно выстроенная архитектура, неграмотное применение паттернов и неправильный выбор концепций (MVC, MVVM и иже с ними) может застопорить разработку и принести существенные убытки бизнесу.

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

Ну и да, сюда же можно отнести и желание самовыразиться через код – когда разработчик применяет какие-то классные, изящные, но понятные только ему решения, самоутверждается таким образом. Никто не против того что вы умные – но такие решения это нарушение методологии. Не надо забывать, что это после вас ещё кому-то отлаживать!

6. Перенесение опыта из других технологических стеков

А вот это уже страшно.

Многие наверняка не очень хорошо понимают, о чём я говорю. Поэтому я приведу пример. Допустим, вы привыкли писать на C# какие-то энтерпрайзно-коммерческие приложения, например, для банковской сферы. Вы привыкли к глубоким абстракциям, микросервисам, разделению всего и вся согласно принципам SOLID, приёмочным тестам — и далее по списку. Вы опытны, ваш опыт работает, ваши приложения стабильны и годны.

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

И вы начинаете противиться этому – пытаетесь унифицировать подход к выполнению всех задач, пытаетесь создать абстракции для типов поведения объектов, пытаетесь отказаться от этого отвратительного Update(), рущащего запросы к web-серверам и красивые структуры данных, и построить «архитектуру» приложения согласно вашему опыту. Ваш опыт говорит вам что вы делаете всё правильно. И все начинают делать «правильно» — но почему-то сроки разработки приложения начинают неумолимо растягиваться, процесс идёт всё медленнее и медленнее, а само приложение жрёт всё больше аппаратных ресурсов и всё менее и менее стабильно.

А причина проста – вы пытаетесь бороться с Unity вместо того чтобы использовать его. И хорошо если вы пересели на Unity c C# а не с Golang-а или, прости Г-споди, FORTRANа.

Повторюсь – архитектура и логика работы Unity сильно отличается от того к чему привыкло большинство программистов, особенно олдовых. Перенесение на него логики работы из других стеков недопустимо. Код, чистый и правильный в других стеках – зачастую говнокод для Unity.

7. Неправильное применение в Unity принципов ООП и паттернов

Эту причину я выделил отдельно, хотя, на первый взгляд, она пересекается с непониманием принципов работы Unity, непониманием принципов программирования и перенесением опыта из других технологических стеков. Но фактически это не так. Здесь речь не о прямом недостатке опыта или переносе навыков а о непонимании неправильности самого Unity.

Поясню, опять же, на примере. Вы опытный юнитист. Вы понимаете как Unity работает. Вы имеете реальное представление об ООП, о принципах SOLID, о паттернах программирования, у вас за плечами десяток успешных проектов. Вы пытаетесь построить архитектуру приложения, стараетесь следовать Unity way, стараетесь разделять ответственность функционала, строите абстракции для поведений…
И всё вроде бы даже работает, стабильно и быстро, и всё правильно и логично… Но разрабатывать всё сложнее и сложнее, код, хотя и правильный на первый взгляд, начинает становиться всё заумнее и запутаннее… И вы ловите себя на том что уже не хотите писать новый функционал, потому что понимаете, что это работает не так как хотелось бы. Хотя странно – всё же сделано правильно.

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

Паттерны же рассчитаны на специфические, «стерильные» условия теоретического программирования.
Unity неправильное. Но это среда, в которой вы работаете. Условия среды надо учитывать.