Правила и рекомендации для разработчиков движка

Введение

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

Язык

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

Многим может показаться полезным распространить правила изложенные здесь на разработку ПО в общем, но наша цель - покрыть только те ситуации, которые наиболее распространены в нашем проекте.

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

Правила работы

#1: Сначала проблема

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

../../_images/best_practices1.png

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

#2: Чтобы решить проблему, сначала убедитесь в её существовании

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

../../_images/best_practices2.png

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

Подход к разработке основанный на вере в то, что проблемы могут возникнуть в любой момент и программа должна быть готова их решить к тому моменту когда они появятся - называется "Разработкой на перспективу" и характеризуется такими идеями как:

  • Я думаю, в будущем, пользователям может быть полезно...

  • Думаю, пользователям в конечном итоге понадобится...

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

#3: Проблема должна быть сложной или частой

Программа разрабатывается для того чтобы решать проблемы, но мы не можем ожидать от неё решения каждой проблемы которая существует во вселенной. Как игровой движок - Godot будет решать проблемы, связанные с созданием игр, это значит что он может помочь вам делать игры лучше и быстрее, но это не значит что он сделает за вас всю игру целиком. Где-то должна быть проведена ограничительная черта.

../../_images/best_practices3.png

Ответ на вопрос: "Cтоит ли решать проблему?" будет зависеть от того, насколько сложно пользователю её обойти. Эта сложность может быть выражена как:

  • Сложность проблемы

  • Частота возникновения проблемы

Если проблема слишком сложна для решения большинством пользователей, программное обеспечение должно предлагать готовое решение для неё. Аналогично, если проблема легко обходится пользователем, предлагать такое решение нет необходимости, и оно должно быть выполнено самим пользователем.

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

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

#4: Решение должно быть обсуждено с другими

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

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

../../_images/best_practices4.png

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

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

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

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

#5: Каждой проблеме - своё решение

Для программистов поиск наиболее оптимальных решений проблем всегда является самым приятным занятием. Однако иногда всё выходит за рамки, и программисты пытаются придумать решения, которые решают как можно больше проблем.

Ситуация часто меняется к худшему, когда для того, чтобы это решение выглядело еще более фантастическим и гибким, на сцену выходят проблемы, основанные на чистой спекуляции (как описано в #2).

../../_images/best_practices5.png

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

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

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

#6: Удовлетворяйте общие потребности, оставляйте дверь открытой для редких случаев использования

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

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

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

Вопрос заключается в том, как разработать программное обеспечение, которое даёт пользователям то, что мы знаем, что им нужно, но при этом достаточно гибкое, чтобы позволить им делать то, что мы не знаем, что им может понадобиться в будущем?

../../_images/best_practices6.png

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

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

#7: Предпочитайте локальные решения

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

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

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

../../_images/best_practices7.png

Обычно желание сделать это объясняется тем, что просто добавить хак в основные слои обычно требует меньше кода.

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

#8: Не используйте сложные готовые решения для простых проблем

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

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

../../_images/best_practices8.png

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

Кроме того, библиотеки должны использовать достаточно разрешительную лицензию, чтобы быть включенными в Godot. Некоторые примеры приемлемых лицензий: Apache 2.0, BSD, MIT, ISC и MPL 2.0. В частности, мы не можем принимать библиотеки, лицензированные под GPL или LGPL, поскольку эти лицензии фактически запрещают статическое связывание в несвободном программном обеспечении (а именно так распространяется Godot в большинстве экспортируемых проектов). Это требование относится и к редактору, поскольку в перспективе мы, возможно, захотим запустить его на iOS. Поскольку iOS не поддерживает динамическое связывание, статическое связывание - единственный вариант на этой платформе.