В этом руководстве приведён ряд рекомендуемых стандартов написания последовательного кода, который легко поддерживать. Придерживаясь эти рекомендаций, разработчики могут повысить читабельность кода, уменьшить количество ошибок и продуктивней сотрудничать друг с другом. Стандартизованный стиль кода упрощает его понимание и поддержку как текущими, так и будущими разработчиками, работающими над проектом.
В этом руководстве содержатся рекомендации, но в итоге выбор остаётся за вашей командой.
1. Общие шаблоны именования
Именование очень важно для удобочитаемости и сопровождения кода. Старайтесь придерживаться одного стиля именования во всем коде.
1.1 Рекомендации по именованию
IsX: часто используется для присвоения названий логическим переменным, отвечающим на вопрос (например, isEmpty).OnX: переопределяемая функция, вызываемая платформой.SubscribeX: подписаться на событие платформы X, при этом для обработки часто используется функция OnX.MakeC: создание экземпляра класса <code>c</code> без переопределения конструктора <code>c</code>.CreateC: создание экземпляра класса <code>c</code> и начало отсчёта его логического времени существования.DestroyC: завершение логического времени существования экземпляра класса <code>c</code>.C:c: если вы работаете с одним экземпляром класса <code>c</code>, можно назвать его 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 := class2.2 В названиях интерфейсов следует использовать прилагательные
По возможности используйте прилагательные в именах интерфейсов, к примеру printable (пригодный для печати), перечислимый (enumerable). Если прилагательные использовать нецелесообразно, добавьте к названию _interface.
my_new_thing_interface := interface2.3 Стиль PascalCase для остальных названий
Для всех прочих имён рекомендуется использовать стиль PascalCase (все слова без пробелов с заглавной буквы, в том числе первое). Для модулей, переменных членов класса, параметров, методов и прочего.
MyNewVariable:my_new_type = …2.4 Параметрические типы
Именуйте параметрические типы
tилиthing, гдеthingпоясняет, что представляет из себя этот тип. Пример:Command(Payload:payload where payload:type)Здесь вы передаёте некоторые параметризованные данныеPayloadлюбого типаpayload.Если существует более одного параметрического типа, избегайте использования одиночных букв, таких как
t,u,gНикогда не используйте суффикс
_t.
3. Форматирование
Форматирование во всей базе кода должно быть унифицированным. Это позволит сделать код более удобочитаемым и понятным как для вас, так и для других разработчиков. Выберите стиль форматирования, который подходит для проекта.
В качестве примера унификации можно выбрать один из следующих форматов использования пробелов во всей базе кода:
MyVariable : int = 5
MyVariable:int = 53.1 Отступы
Используйте четыре пробела для отступа, а не табуляцию.
В блоках кода следует использовать блоки с отступами (с пробелами), а не фигурные скобки:
Versemy_class := class: Foo():void = Print("Hello World")За исключением однострочных выражений, таких как
option{a},my_class{A := b}и т. д.
3.2 Пробелы
Оставляйте пробелы вокруг операторов, если только не требуется обеспечивать компактность кода в связи с его контекстом. Добавляйте скобки, чтобы однозначно определить порядок операций.
VerseMyNumber := 4 + (2 * (a + b))Не ставьте пробелы после открывающих и перед закрывающими скобками. Несколько выражений внутри скобок следует разделять одним пробелом.
VerseMyEnum := enum{Red, Blue, Green} MyObject:my_class = my_class{X := 1, Y := 2} Vector := vector3{Left := 1000.0, Up := -1000.0, Forward := 0.0} Foo(Num:int, Str:[]char)Между идентификатором и типом не должно быть пробелов; пробелы необходимо ставить вокруг оператора присваивания
=. Ставьте пробелы вокруг определений типов и операторов инициализации констант (:=).VerseMyVariable:int = 5 MyVariable := 5 my_type := classСледуйте тем же рекомендациям в отношении пробелов вокруг скобок, идентификаторов и типов при определении сигнатур функций.
VerseFoo(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 Разрывы строк
Чтобы вставить разрывы строк, записывайте код в нескольких строках с интервалами.
Делайте так
VerseMyTransform := transform: Translation := vector3: Left := 100.0 Up := 200.0 Forward := 300.0 Rotation := rotation: Pitch := 0.0 Yaw := 0.0 Roll := 0.0Более удобочитаемый и легче редактировать.
Так не делайте
VerseMyTransform := transform{Translation := vector3{Left := 100.0, Up := 200.0, Forward := 300.0}, Rotation := rotation{...}}Неудобно читать, когда всё в одной строке.
Записывайте элементы перечисления в нескольких строках с интервалами, если нужно вставить комментарий для каждого из них или разрыв строки.
Verseenum: Red, # Desc1 Blue, # Desc2
3.4 Скобки
Не используйте скобки в определениях ненаследуемых классов.
Делайте так | Verse |
Так не делайте | Verse |
3.5 Избегайте обозначений с пробелом после точки
Избегайте использования точки с пробелом «. » вместо скобок. В этом случае сложно оценить число пробелов на глаз, и это может стать причиной ошибок.
Так не делайте | Verse |
Так не делайте | Verse |
4. Функции
4.1 Неявный возврат по умолчанию
Функции возвращают значение последнего выражения. Используйте это как способ неявного возврата.
Sqr(X:int):int =
X * X # Implicit returnЕсли используется явный возврат, все возвращаемые значения функции должны быть явными.
4.2 Функции GetX
Методы получения или функции с аналогичной семантикой, которые могут завершаться с ошибкой либо возвращать допустимые значения, должны иметь пометку <decides><transacts> и возвращать тип, не являющийся типом option. За обработку потенциальной ошибки отвечает вызывающая функция.
GetX()<decides><transacts>:xИсключением являются функции, которым требуется безусловная запись в переменную var. Ошибка приведёт к откату изменений, поэтому их возвращаемое значение должно иметь тип logic или option.
4.3 Используйте методы расширения вместо функций с одним параметром
Используйте методы расширения вместо функций с одним типизированным параметром.
В этом вам поможет система автодополнения ввода. Если набрать MyVector.Normalize() вместо Normalize(MyVector), то при поиске будут предлагаться названия с каждым символом введённого вами названия метода.
Делайте так | Verse |
Так не делайте | Verse |
5. Проверки на ошибки
5.1 Записывайте в одной строке не более трёх выражений с неоднозначным результатом
Записывайте не более трёх проверок условий/выражений с неоднозначным результатом в одной строке.
Verseif (Damage > 10, Player := FindRandomPlayer[], Player.GetFortCharacter[].IsAlive[]): EliminatePlayer(Player)Используйте
ifсо скобками(), когда количество условий меньше трёх.
Делайте так | Verse | Код краткий и удобочитаемый. |
Так не делайте | Verse | Код безосновательно разбит на несколько строк без повышения удобочитаемости. |
Если в каждом выражении используется более двух слов, то для сохранения удобочитаемости в одной строке следует записывать максимум два выражения.
Verseif (Player := FindAlivePlayer[GetPlayspace().GetPlayers()], Team := FindEmptyTeam[GetPlayspace().GetTeamCollection().GetTeams()]): AddPlayerToTeam(Player, Team)Вы также можете применить это правило при прописывании контекста с возможным отсутствием результата не более девяти слов в одной строке. Если лимит превышен, используйте многострочную форму с интервалами.
Делайте так | Verse | Текст более удобочитаемый, а контекст более понятен благодаря разбивке на несколько строк. |
Так не делайте | Verse | Текст сложно анализировать. |
Проверьте, упростит ли группировка нескольких условий с возможным отсутствием результата в одну функцию
<decides>чтение кода и его повторное использование. Обратите внимание, что если код используется только в одном месте, будет достаточно добавить комментарий к разделу без создания специальной функции.Verseif: Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 then: EliminatePlayer(Player)Можно переписать так:
VerseGetRandomPlayerToEliminate()<decides><transacts>:player= Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 Player if (Player := GetRandomPlayerToEliminate[]): Eliminate(Player)То же самое правило применимо к выражениям в циклах
for. Пример:Verseset Lights = for (ActorIndex -> TaggedActor : TaggedActors, LightDevice := customizable_light_device[TaggedActor], ShouldLightBeOn := LightsState[ActorIndex]): Logger.Print("Adding Light at index {ActorIndex} with State:{if (ShouldLightBeOn?) then "On" else "Off"}") if (ShouldLightBeOn?) then LightDevice.TurnOn() else LightDevice.TurnOff() LightDeviceЛучше записать код так:
Verseset 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.
Это улучшает локальность кода, что упрощает его логическое понимание и отладку.
Делайте так | Verse | Зависимые или связанные условия сгруппированы. |
Делайте так | Verse | Зависимые или связанные условия сгруппированы. |
Так не делайте | Verse | Ненужные отступы могут затруднить восприятие кода. |
Так не делайте | Verse | Ненужные отступы могут затруднить восприятие кода. |
Допускается разделять контексты с возможным отсутствием результата, если нужно обрабатывать каждую возможную ситуацию отсутствия результата (или группу таких ситуаций) по отдельности.
if (Player := FindPlayer[]):
if (Player.IsVulnerable[]?):
EliminatePlayer(Player)
else:
Print("Player is invulnerable, can’t eliminate.")
else:
Print("Can’t find player. This is a setup error.")6. Инкапсуляция
6.1 Используйте интерфейсы вместо классов
По возможности используйте интерфейсы вместо классов. Это позволит сократить зависимость от реализации и даст пользователям возможность прописывать реализации, которые могут использоваться инфраструктурой.
6.2 Используйте закрытое (private) обращение и ограничивайте область видимости
В большинстве случаев члены класса должны быть 'закрытыми'.
Область видимости методов класса и модуля должна быть как можно более ограниченной — <internal> или <private>, в зависимости от обстоятельств.
7. События
7.1 События с постфиксом Event и обработчики с префиксом On
Имена событий, на которые можно подписаться, а также списков делегатов должны иметь постфикс `Event`, а имена обработчиков событий должны иметь префикс `On`.
MyDevice.JumpEvent.Subscribe(OnJump)8. Одновременное выполнение
8.1 Не добавляйте в функции слово Async
Не добавляйте в названия функций <suspends> слово Async или другие подобные слова.
Делайте так | Verse |
Так не делайте | Verse |
Можно добавить префикс Await в название функции <suspends>, которая в своём теле будет ожидать выполнения какого-либо действия.
Это поможет понять, как должен использоваться API.
AwaitGameEnd()<suspends>:void=
# Setup other things before awaiting game end…
GameEndEvent.Await()
OnBegin()<suspends>:void =
race:
RunGameLoop()
AwaitGameEnd()9. Атрибуты
9.1 Разделяйте атрибуты
Записывайте атрибуты в отдельных строках. Это повышает удобочитаемость, особенно если к одному и тому же идентификатору добавлено несколько атрибутов.
Делайте так | Verse |
Так не делайте | Verse |
10. Выражения импорта
10.1 Записывайте выражения импорта в алфавитном порядке
Пример:
using { /EpicGames.com/Temporary/Diagnostics }
using { /EpicGames.com/Temporary/SpatialMath }
using { /EpicGames.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }