Следующий доклад с Pixonic DevGAMM Talks, который мы расшифровали, немного философский — это выступление Константина Гладышева. Он Lead Game Programmer в 1C Game Studios и рассказывал о принципе управления сложностью разработки в контексте всего продукта, а не отдельных фичей. И на примерах показал, почему главное в разработке — это определить, чего делать не надо. Про другие доклады можно почитать по ссылкам в конце статьи.
Хотел рассказать про севера, но решили сделать философский доклад, что надо делать проще. Я работал над Postal III, Indestructible, War Robots и сейчас делаю Caliber — сессионный онлайн-шутер от 1С и Wargaming.
Почему мы решили поговорить о KISS (Keep it simple, stupid)? Потому что даже сеньоры с 20-летним опытом или CTO часто продолжают что-то лепить и придумывать. А нужно делать проще. На самом деле тут больше про YAGNI (You ain’t gonna need it) и немного философии.
Сразу отмечу, что мы не говорим про совсем идиотские решения, типа «найди х», мы говорим про более-менее простые решения.
Почему иногда бывает слишком сложно? Все начинается из благих намерений, а они, как известно, ведут в ад. Вот комикс по этому поводу.
Причины этого примерно одинаковые, но я по-разному их назвал:
- Универсальные решения. Есть какая-то фича и мы сразу делаем из нее библиотеку, миллион дополнительных кейсов. А то вдруг наш шутер превратится в ферму? Или «я буду в следующий раз делать еще один точно такой же шутер». Вряд ли, скорее всего он у вас поменяется.
- Future proof. Практически то же самое — это несуществующие проблемы. «А если у меня будет миллион юзеров сразу, мне нужно выдержать 22 промежуточных сервера?» и т.д. Чтобы начать зарабатывать, вам хватит и одного сервера на всё.
- Использование неправильных инструментов. Когда вы используете инструменты, которые не подходят и упорствуете в своем заблуждении.
- И всем известная преждевременная оптимизация.
У нас был пример, когда делали Caliber. Парни, которые тогда нам помогали, решили сразу сделать суперсериализатор на последнем С++.
В итоге он не работал, как хотелось, было неудобно отправлять частичный стейт, потому что шаблоны не очень понимают, где надо, а где нет или делаешь какие-то флаги. Баги, которые были в этом шаблонном коде, не понимал со временем даже автор.
Тогда один из программистов буквально за два часа переписал всё это добро на одну страницу С кода, который делал только то, что прямо тогда нам было нужно. И оно работало чудесно.
Другой пример. У нас был Postal III и он делался на движке Source. Там такой открытый мир, можно ходить между картами, камера от третьего лица, много одноэтажных домиков в американском стиле с окнами, боты могут бегать и в панике открывать двери. В итоге весь BSP не работал. Он очень долго считался, из-за окон получался миллион секторов и все равно ничего не делал. Он занимал много памяти и долго грузился.
Half-Life, для которого был сделан движок — это шутер от первого лица, а от третьего некоторые вещи делать было неудобно. Всё полезное от Half-Life нам не подходило совсем. Также предполагалось большое количество анимации, потому что от третьего лица нужно перелезать и т.д. Надо было менять движок, но тогда варианта не было.
Что же делать, когда все плохо, сложно, а нас подгоняют? Во-первых, делать фичи в правильном порядке, потому что оптимизирование раньше времени — одна из проблем. Некоторые начинают наклеивать стразики до того, как сшили платье, а потом шить не могут, потому что стразики мешают.
Сначала делайте самую простую фишку, чтобы она просто работала, потом стабилизируйте, а потом — оптимизируете. Именно в этом порядке, всё остальное — неправильно.
Под визуализацией мы понимаем, что она минимально рабочая, т.е. MVP (minimum viable product).
Дальше вы оцениваете её потенциал. Допустим, геймдизайнеры придумывают фичи, программист прибегает и говорит «я буду делать хорошо, плохо не буду, поэтому сразу рисуйте обалденный геймдизайн». Но откуда он знает? Он не играл, не знает, хорошо это или плохо. Поэтому в идеале вы делаете фичу, играете, если нормально — значит делаете её дальше. Не нормально — выкинули, не жалко.
Еще тысячу лет назад Сунь Цзы говорил, что одержать победу в 100 битвах это не вершина, вершина — выиграть без сражения. Т.е. не занимайтесь тем, что можно не делать.
Стабилизация. Вы сделали фичи и дальше стабилизируете её без лишних дополнений. Вам нужно колесо на дереве, на веревке? Оно и висит. Больше ничего.
Соответственно, если вдруг обнаружилось (без всяких future proof), что фича должна поменяться — начинайте заново. Просто делайте прототип, стабилизируйте, а не пытайтесь наперед угадать. Всё равно не угадаете.
Ну и преждевременная оптимизация. Это всегда плохо. Оптимизировать нужно в самом конце, когда вы точно знаете, что эта фича важная, ее стоит оптимизировать и она в ближайшее время принципиально не изменится.
Потому что оптимизация — это набор частных случаев. Ухудшается читаемость кода, ломаются абстракции и портируемость, как правило, тоже сильно ухудшается.
Это провокационный слайд, потому что на самом деле костыли — это плохо. Но тут показана ситуация, когда есть куча унылых фичей и прототипов, все плохо, и кажется, что всё разрушится. Но это не так. Посмотрите — это опалубка, в нее заливается бетон, а «костыли» подпирают, пока он сохнет. Т.е. ситуация абсолютно нормальная, «костыли» потом уберут, но не сразу, без паники.
Вкратце о философии. Нет универсально верных решений. Не пытайтесь сделать фреймворк на 100 лет вперед или на все случаи жизни.
Лучше сделайте много мелких кусочков, которые хорошо делают свое дело. Выкидывайте ненужное при первой возможности, не пытайтесь это саппротить. И когда пишите код — делайте его явным. Иногда лучше написать явный код, который сериализуете руками, вместо рефлекшена или еще чего-нибудь. Использовать dependency action, когда он вам не нужен, тоже плохая идея. Это очень тяжело читается, половина ошибок в рантайме. Явное лучше неявного. И делайте как можно проще, чтобы избежать ошибок, когда кто-то что-то не понял или вообще забыл.
Как говорил Брюс Ли, простота — высшая степень искусства. Однажды актер, которого он обучал своему Джит Кун-До, спросил у него: «В чем суть твоего боевого искусства Джит Кун-До?». В этот момент Брюс Ли уронил кошелек, актер подобрал и Брюс Ли сказал: «Вот видишь, ты просто нагнулся и подобрал кошелек, а если бы ты встал в позу всадника, делал каты — ты бы никогда его не поднял».
Вопросы из зала
— Ты говорил, что преждевременную оптимизацию — это зло. А стоит писать преждевременные тесты, когда проект только начинается?
— В моем понимании вообще все преждевременное вредно. В тестах я не очень силен, потому что в геймдеве (во многих случаях) тесты ближе к концу разработки. Вначале все так быстро меняется, что пока напишешь тест, геймдизайнер уже все поменяет. Я считаю, что плохо тратить силы на тест того, что через два часа изменится. Это нужно делать на этапе стабилизации. Но не на этапе прототипа. Но если команда умеет быстро тесты писать, наверное, это хорошо. Если нет, то нет.
— Ты упомянул, что костыли будут убраны, но есть замечательный тезис — нет ничего более постоянного, чем временное. Все мы работаем в геймдеве, у нас сроки, продюсеры, потом новая фича и так далее. Как часто ты видел ситуацию, когда вы вычищали костыли? И убирали ли их в ноль?
— В ноль, наверное, нет. Если проект живой, у тебя всегда будут какие-то костыли. Это все работает, если их чистить систематически. Т.е. вы сделали фичу — тут же зафиксировали.
— Т.е. должен быть построен некий процесс в компании? Типа официальной фазы чистки костылей?
— Да, я считаю что это должно входить в таск. Т.е. если нужно зарефакторить в процессе таска — рефакторишь. Грубо говоря, даже слова рефакторинг нет — это внутри таска.
— А удается это делать на практике? Приходишь к продюсеру и говоришь на планировании новой итерации, что нам нужно две недели, чтобы почистить, порефакторить и т.д. А он говорит, что бизнес велью ноль, сейчас делаем фичу х, а это вы потом как-нибудь вечером после работы почистите. Как быть в такой ситуации?
— У нас удается. Продюсер в курсе, что есть какие-то долги, которые ты исправляешь, но своевременно. А не так, что у тебя новый релиз, в нем ни одной новой фичи, а тут какой-то рефакторинг. Просто выбирайте правильного продюсера.
— С опытом приходит понимание, чем проще — тем лучше. Но начинающие программисты стараются создавать сложные, страшные, большие и гигантские системы. Вопрос: как удержать их от этого, кроме жёсткого «нет»?
— Мне кажется, это проблема обучения. Нужно как можно раньше показывать, какие решения работают, какие нет и почему. Когда у тебя есть опыт и ты можешь объяснить, почему так делать не надо, ты можешь просто привести массу примеров и это уже хорошо работает. Собственным примером показывать и постоянно следить за тем, чтобы всё было просто. Посадить на прототипы, чтобы они чаще переписывали — когда часто переписываешь, много писать уже не хочется и они пишут всё проще и проще.
— Если уже есть что-то очень сложное, что используется на нескольких проектах, и эта сложность уже не помогает, а скорее мешает — как проще перейти к простым решениям?
— Мое мнение, просто начать делать заново. В идеале — какая-то отдельная команда, прямо с нуля. Скорее всего вы восстановите 80% функциональности очень быстро. На новой и чистой библиотеке. А дальше будете догонять.
— К примеру, есть какой-то мощный сериализатор и редактор игровой логики, вот он достаточно устарел...
— Это те самые неудобные инструменты. Возьмите удобные. Unity, например.
— Расскажи, какое у вас планирование в коде, насколько оно детальное? Главный программист решает все мелкие вопросы, все задачи?
— У нас такая анархия, достаточно плоская структура, не очень много людей. Мы доверяем всем и просто распределяем, кто будет брать прототип, а кто нет. Это может быть любой произвольный человек.
Еще доклады с Pixonic DevGAMM Talks
- Использование Consul для масштабирования stateful-сервисов (Иван Бубнов, DevOps в компании BIT.GAMES);
- CICD: бесшовный деплой на распределенные кластерные системы без даунтаймов (Егор Панов, системный администратор Pixonic);
- Практика использования модели акторов в бэкэнд-платформе игры Quake Champions (Роман Рогозин, backend-разработчик Saber Interactive);
- Архитектура мета-сервера мобильного онлайн-шутера Tacticool (Павел Платто, Lead Software Engineer в PanzerDog);
- Как ECS, C# Job System и SRP меняют подход к архитектуре (Валентин Симонов, Field Engineer в Unity).
Автор: Никита Гук