Рекомендации
Руководство по оформлению кода Verse содержит ряд рекомендаций по написанию унифицированного и удобного в сопровождении кода. Придерживаясь эти рекомендаций, разработчики могут повысить читабельность кода, уменьшить количество ошибок и продуктивней сотрудничать друг с другом. Стандартизованный стиль кода упрощает его понимание и поддержку как текущими, так и будущими разработчиками, работающими над проектом.
В этом руководстве содержатся рекомендации, но в итоге выбор остаётся за вашей командой.
1. Общие шаблоны именования
Именование очень важно для удобочитаемости и сопровождения кода. Старайтесь придерживаться одного стиля именования во всем коде.
1.1 Рекомендации по именованию
IsX
— часто используется для присвоения имён логическим переменным, отвечающим на вопрос (например, isEmpty).
-
OnX
— переопределяемая функция, вызываемая инфраструктурой. -
SubscribeX
— подписка на событие инфраструктуры X, при этом для обработки часто используется функция OnX. -
MakeC
— создание экземпляра классаc
без переопределения конструктораc
. -
CreateC
— создание экземпляра классаc
и инициирование его логического существования. -
DestroyC
— завершение логического существования экземпляра классаc
. -
C:c
— если вы работаете над одним экземпляром классаc
, можно назвать его C.
1.2 Чего не стоить делать
-
Перегружать имена типов. Используйте имя
thing
, а неthing_type
илиthing_class
. -
Перегружать значения элементов перечисления. Не
color := enum{COLOR_Red, COLOR_Green}
, аcolor := enum{Red, Green}
.
2. Имена
2.1 Для типов используйте нижний регистр с символами подчёркивания (lower_snake_case)
Все типы следует именовать в нижнем регистре с разделением слов символом подчёркивания: lower_snake_case
. Так именуются структуры, классы, определения типов, типажи/интерфейсы, элементы перечислений и т. д.
my_new_type := class
2.2 В именах интерфейсов следует использовать прилагательные
По возможности используйте прилагательные в именах интерфейсов, к примеру printable (пригодный для печати), перечислимый (enumerable). Если прилагательные использовать нецелесообразно, добавьте к имени _interface.
my_new_thing_interface := interface
2.3 Стиль PascalCase для остальных имён
Для всех прочих имён рекомендуется использовать стиль PascalCase (все слова без пробелов с заглавной буквы, в том числе первое). Для модулей, переменных членов класса, параметров, методов и прочего.
MyNewVariable:my_new_type = …
2.4 Параметрические типы
-
Именуйте параметрические типы
t
илиthing
, гдеthing
поясняет, что представляет из себя этот тип. Пример:Send(Payload:payload where payload:type)
Здесь вы передаёте некоторые параметризованные данныеPayload
любого типаpayload
. -
Если существует более одного параметрического типа, избегайте использования одиночных букв, таких как
t
,u
,g
. -
Никогда не используйте суффикс
_t
.
3. Форматирование
Форматирование во всей базе кода должно быть унифицированным. Это позволит сделать код более удобочитаемым и понятным как для вас, так и для других разработчиков. Выберите стиль форматирования, который подходит для проекта.
В качестве примера унификации можно выбрать один из следующих форматов использования пробелов во всей базе кода:
MyVariable : int = 5
MyVariable:int = 5
3.1 Отступы
-
Используйте отступ в четыре пробела, а не табуляцию.
-
В блоках кода следует использовать блоки с отступами (с интервалами), а не фигурные скобки:
my_class := class: Foo:void = Print("Привет, мир")
- За исключением однострочных выражений, таких как
option{a}
,my_class{A := b}
и т. д.
- За исключением однострочных выражений, таких как
3.2 Пробелы
Вокруг операторов рекомендуется оставлять пробелы, если только не требуется обеспечивать компактность кода в связи с его контекстом. Добавляйте скобки, чтобы однозначно определить порядок операций.
MyNumber := 4 + (2 * (a + b))
-
Не ставьте пробелы после открывающей и перед закрывающей скобками. Несколько выражений внутри скобок следует разделять одним пробелом.
enum{Red, Blue, Green} MyObject:my_class = my_class{X := 1, Y := 2} Vector := vector3{X := 1000.0, Y := -1000.0, Z := 0.0} Foo(Num:int, Str:[]char)
- Между идентификатором и типом не должно быть пробелов. Пробелы необходимо ставить вокруг оператора присваивания
=
. Ставьте пробелы вокруг определений типов и операторов инициализации констант (:=
).MyVariable:int = 5 MyVariable := 5 my_type := class
- Следуйте тем же рекомендациям в отношении пробелов вокруг скобок, идентификаторов и типов при определении сигнатур функций.
Foo(X:t where t:subtype(class3)):tuple(t, int) = (X, X.Property) Foo(G(:t):void where t:type):void Const(X:t, :u where t:type, u:type):t = X
3.3 Разрывы строк
-
Если требуется вставить разрыв строки, используйте многострочную форму с интервалами.
Правильно MyTransform := transform: Translation := vector3: X := 100.0 Y := 200.0 Z := 300.0 Rotation := rotation: Pitch := 0.0 Yaw := 0.0 Roll := 0.0
Более удобочитаемо и легче редактировать. Неправильно MyTransform := transform{Translation := vector3{X := 100.0, Y := 200.0, Z := 300.0}, Rotation := rotation{…}}
Неудобно читать, когда всё в одной строке.
- Указывайте элементы перечисления в нескольких строках с интервалами, если хотите вставить комментарий для каждого из них или разрыв строки.
enum: Red, # Описание 1 Blue, # Описание 2
3.4 Скобки
Не используйте скобки в определениях ненаследуемых классов.
Правильно |
|
Неправильно |
|
3.5 Избегайте обозначений с пробелом после точки
Избегайте использования точки с пробелом «. » вместо скобок. Это визуально затрудняет анализ пробелов и может запутать читателя.
Неправильно |
|
Неправильно |
|
4. Функции
4.1 Неявный возврат по умолчанию
Функции возвращают значение последнего выражения. Используйте это как способ неявного возврата.
Sqr(X:int):int =
X * X # Неявный возврат
Если используется явный возврат, все возвращаемые значения функции должны быть явными.
4.2 Функции GetX должны иметь спецификаторы <decides><transacts>
Методы чтения или функции с аналогичной семантикой, которые могут завершаться с ошибкой либо возвращать допустимые значения, должны иметь спецификаторы <decides><transacts>
и возвращать тип, не являющийся типом option. За обработку потенциальной ошибки отвечает вызывающая функция.
GetX()<decides><transacts>:x
Исключением являются функции, которым требуется безусловная запись в var
. Ошибка приведёт к откату изменений, поэтому их возвращаемое значение должно иметь тип logic
или option
.
4.3 Используйте методы расширения вместо методов с одним параметром
Используйте методы расширения вместо модульного метода с одним типизированным параметром.
В этом вам поможет система автодополнения ввода. Если набрать MyVector.Normalize()
вместо Normalize(MyVector)
, эта система будет предлагать имена с каждым символом введённого вами имени метода.
Правильно |
|
Неправильно |
|
5. Проверки на ошибки
5.1 Записывайте в одной строке не более трёх выражений с возможной ошибкой
-
Записывайте не более трёх проверок условий/выражений с возможной ошибкой в одной строке.
if (Damage > 10, Player := FindRandomPlayer[], Player.GetFortCharacter[].IsAlive[]): EliminatePlayer(Player)
-
Используйте
if
со скобками()
, когда количество условий меньше трёх.
Правильно |
|
Код краткий и одновременно удобочитаемый. |
Неправильно |
|
Код безосновательно разбит на несколько строк без повышения удобочитаемости. |
-
Если в каждом выражении используется более двух слов, то для сохранения удобочитаемости в одной строке следует записывать максимум два выражения.
if (Player := FindAlivePlayer[GetPlayspace().GetPlayers()], Team := FindEmptyTeam[GetPlayspace().GetTeamCollection().GetTeams()]): AddPlayerToTeam(Player, Team)
-
Рекомендуется не использовать более девяти слов в одной строке при прописывании контекста, допускающего ошибки. Если лимит превышен, используйте многострочную форму с интервалами.
Правильно |
|
Текст более удобочитаемый, а контекст более понятен благодаря большему числу строк. |
Неправильно |
|
Текст сложно анализировать. |
- Проверьте, упростит ли группировка нескольких условий с возможной ошибкой в одну функцию
<decides>
чтение кода и его повторное использование. Обратите внимание, что если код используется только в одном месте, будет достаточно добавить комментарий раздела без специальной функции.
if:
Player := FindRandomPlayer[]
IsAlive[Player]
not IsInvulnerable[Player]
Character := Player.GetFortCharacter[]
Character.GetHealth < 10
then:
EliminatePlayer(Player)
Можно представить как
GetRandomPlayerToEliminate()<decides><transacts>:player=
Player := FindRandomPlayer[]
IsAlive[Player]
not IsInvulnerable[Player]
Character := Player.GetFortCharacter[]
Character.GetHealth < 10
Player
if (Player := GetRandomPlayerToEliminate[]):
Eliminate(Player)
- То же самое правило применимо к выражениям в циклах for. Пример:
set Lights = for (ActorIndex -> TaggedActor : TaggedActors, LightDevice := customizable_light_device[TaggedActor], ShouldLightBeOn := LightsState[ActorIndex]):
Logger.Print("Добавляю источник освещения с индексом {ActorIndex} с состоянием:{if (ShouldLightBeOn?) then "Вкл." else "Выкл."}")
if (ShouldLightBeOn?) then LightDevice.TurnOn() else LightDevice.TurnOff()
LightDevice
Более удобочитаемый вариант:
set Lights = for:
ActorIndex -> TaggedActor : TaggedActors
LightDevice := customizable_light_device[TaggedActor]
ShouldLightBeOn := LightsState[ActorIndex]
do:
if (ShouldLightBeOn?) then LightDevice.TurnOn() else LightDevice.TurnOff()
LightDevice
5.2 Группируйте зависимые выражения c возможной ошибкой
Если условие в контексте, допускающем ошибки, зависит от успешного выполнения предыдущего контекста, допускающего ошибки, по возможности объедините два условия в одном контексте, допускающем ошибки, и следуйте рекомендации 5.1.
Это улучшает локальность кода, что упрощает его логическое понимание и отладку.
Правильно |
|
Зависимые или связанные условия сгруппированы. |
Правильно |
|
Зависимые или связанные условия сгруппированы. |
Неправильно |
|
Ненужные отступы могут затруднить восприятие кода. |
Неправильно |
|
Ненужные отступы могут затруднить восприятие кода. |
Допускается разделять контексты, допускающие ошибки, если нужно обрабатывать каждую возможную ошибку (или группу ошибок) по отдельности.
if (Player := FindPlayer[]):
if (Player.IsVulnerable[]?):
EliminatePlayer(Player)
else:
Print("Игрок неуязвим, его невозможно устранить.")
else:
Print("Невозможно найти игрока. Ошибка настройки.")
6. Инкапсуляция
5.1 Используйте интерфейсы вместо классов
По возможности используйте интерфейсы вместо классов. Это позволит сократить зависимость от реализации и даст пользователям возможность прописывать реализации, которые могут использоваться инфраструктурой.
5.2 Используйте закрытый (частный) доступ и ограничьте область видимости
В большинстве случаев члены класса должны быть 'закрытыми'.
Область видимости методов класса и модуля должна быть как можно более ограниченной — 'внутренней' или 'закрытой', в зависимости от обстоятельств.
6. События
6.1 События с постфиксом 'Event' и обработчики с префиксом 'On'
Имена событий, на которые можно подписаться, а также списков делегатов должны иметь постфикс 'Event', а имена обработчиков событий должны иметь префикс 'On'.
MyDevice.JumpEvent.Subscribe(OnJump)
7. Одновременное выполнение
7.1 Не добавляйте в функции '' слово 'Async'
Не добавляйте в имена функций '
Правильно |
|
Неправильно |
|
Можно добавить префикс 'Await' к функции '
AwaitGameEnd()<suspends>:void=
# Настройте остальные параметры, не дожидаясь окончания игры…
GameEndEvent.Await()
OnBegin()<suspends>:void =
race:
RunGameLoop()
AwaitGameEnd()
8. Атрибуты
8.1 Разделяйте атрибуты
Записывайте атрибуты в отдельных строках. Это повышает удобочитаемость, особенно если к одному и тому же идентификатору добавлено несколько атрибутов.
Правильно |
|
Неправильно |
|
9. Конструкции 'using'
9.1 Записывайте конструкции 'using' в алфавитном порядке
Пример:
using { /EpicGames.com/Temporary/Diagnostics }
using { /EpicGames.com/Temporary/SpatialMath }
using { /EpicGames.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }