Note
Апстрим документация tg где-то может быть более подробна и актуальна, но не обязательно соответствует нашему билду.
Code Convention - свод несложных правил по оформлению кода. Его наличие подразумевает, что все кто вносят свои изменения, с ним соглашаются и руководствуются им в полной мере. Обязательно прочтите всю статью, если зашли сюда в первый раз. Это очень важно.
В случае если что-то не было здесь оговорено, вы можете уточнить этот момент в конфе. Хотя скорее всего это значит, что в том, что вас интересует вы вольны думать самостоятельно.
В качестве отступов для компилируемых блоков кода используются только табы. Если код относится к комментарию (то есть для примера), то выравнивать рекомендуется пробелами.
Между вызовом процедуры/функции и скобками не должно быть пробела.
//Плохо:
/proc/foo()
return function (1)
//Хорошо:
/proc/foo()
return function(1)
Не должны писаться одной строкой. Иногда, если так действительно будет лучше, допускается однострочные switch
и else if
конструкции.
//Плохо:
if(something) return TRUE
for(var/i in something) i.foo()
//Хорошо:
if(something)
return TRUE
for(var/i in something)
i.foo()
//Допустимо:
switch(x)
if(case_1) foo()
if(case_2) return FALSE
В связи с некоторой непредсказуемостью результатов сложных выражений включающих in, любую проверку на наличие в списке стоит брать в скобки.
//Плохо:
if(foo && bar in list)
if(bar in arr and foo)
//Хорошо:
if(foo && (bar in list))
if((bar in arr) and foo)
Один пробел перед оператором и один пробел после. Исключение - побитовые. Пробелы при их использовании вставляйте на своё усмотрение.
//Плохо:
var/a=1
var/b = 2
var/c =a+b
//Хорошо:
var/a = 1
var/b = 2
var/c = a + b
Следуйте единому формату. Так или иначе пробел после запятой - обязателен.
//Плохо:
list( 1,2, 3, 4 ,5)
//Хорошо:
list(1, 2, 3, 4, 5)
//Допустимо:
list( 1 , 2 , 3 , 4 , 5 )
Форматирование только через пробелы. Обратите внимание на выравнивание.
//Плохо:
#define A "something" // Это что-то.
#define B "anything" //Это что угодно.
//Хорошо:
#define A "something" // Это что-то.
#define B "anything" // Это что угодно.
Например, при объявлении элементов списка - преимущественно пробелами.
//Плохо:
list(1, 2, 3,
4, 5, 6)
//Хорошо:
list(1, 2, 3,
4, 5, 6)
//Допустимо:
list(
1, 2, 3,
4, 5, 6
)
Все пути в обязательном порядке должны быть абсолютными.
//Плохо:
obj
var
varname1 = 0
varname2
proc
proc_name()
code
item
weapon
name = "Weapon"
proc
proc_name2()
..()
code
//Хорошо:
/obj
var/varname1 = 0
var/varname2
/obj/proc/proc_name()
code
/obj/item/weapon
name = "Weapon"
/obj/item/weapon/proc/proc_name2()
..()
code
//Плохо:
mob/living
//Хорошо:
/mob/living
//Плохо:
var/path = "/obj/item/something"
//Хорошо:
var/path = /obj/item/something
Записывать путь объекта строкой небезопасно, так как в случае если каким-то образом тот изменится или вообще удалится из билда, компилятор не сообщит о попытке использования несуществующего типа.
Не должно быть конструкций типа var/
и as ...
//Плохо:
/proc/foo(var/atom/A as area|turf|obj|mob)
//Хорошо:
/proc/foo(atom/A)
Использование запрещено. Всегда приводите объект к нужному вам типу.
//Плохо:
/proc/foo(atom/A)
return A:some_var
//Хорошо:
/proc/foo(atom/A)
var/obj/O = A
return O.some_var
Данный оператор отрабатывает в рантайме, проходя по всем подтипам объекта в поисках запрашиваемой переменной, соответственно его использование чревато проблемами производительности.
proc().var
и list[index].var
- подобные конструкции равнозначны использованию :
и их также не следует использовать по тем же причинам.
//Плохо:
var/count = foo().len
//Хорошо:
var/list/L = foo()
var/count = L.len
Если не знаете что это, то прочитайте эту статью Магическое число (программирование). Соответственно их быть не должно. Используйте дефайны или константные переменные.
//Плохо:
/proc/foo(variable)
switch(variable)
if(1)
do something
if(2)
return
//Хорошо:
#define CASE_1 1
#define CASE_2 2
/proc/foo(variable)
switch(variable)
if(CASE_1)
do something
if(CASE_2)
return
#undef CASE_1
#undef CASE_2
Вместо 1 и 0 используйте TRUE
и FALSE
соответственно.
Используйте только тогда, когда это необходимо.
//Плохо:
/mob/some/class/proc/foo()
src.some_var = some_value
src.another_var = another_value
//Хорошо:
/mob/some/class/proc/foo()
some_var = some_value
another_var = another_value
//Пример необходимого использования:
/mob/some/class/proc/foo(some_var, another_var)
src.some_var = some_var
src.another_var = another_var
Приведение типов в циклах for(var/some/foo in bar)
содержит внутреннюю проверку istype()
, которая отсеивает неподходящие типы, включая null
. Ключевое слово as anything
позволяет пропустить ненужные проверки.
Если вы знаете, что лист содержит только нужные типы, то вы бы хотели не только пропустить лишние проверки ради небольшой оптимизации, но и для обнаружения любых null
-элементов, которые могут быть в этом листе.
Обычно, null
в листе означает, что как-то неправильно были обработаны ссылки, что затрудняет отладку хард делов.
var/list/bag_of_atoms = list(new /obj, new /mob, new /atom, new /atom/movable, new /atom/movable)
var/highest_alpha = 0
//Плохо:
for(var/atom/thing in bag_of_atoms)
if(thing.alpha <= highest_alpha)
continue
highest_alpha = thing.alpha
//Хорошо:
for(var/atom/thing as anything in bag_of_atoms)
if(thing.alpha <= highest_alpha)
continue
highest_alpha = thing.alpha
//Допустимо:
for(var/atom in bag_of_atoms)
var/atom/thing = atom
if(thing.alpha <= highest_alpha)
continue
highest_alpha = thing.alpha
На простом примере, если мы хотим, чтобы моб терял 2 жизни в секунду:
//Плохо:
/mob/testmob
var/health = 100
var/health_loss_per_tick = 4 //Мы хотим терять 2 здоровья в секунду
/mob/testmob/process() //сабсистема SSmobs запускается каждые 2 секунды
health -= health_loss_per_tick
//Хорошо:
/mob/testmob
var/health = 100
var/health_loss_per_second = 2
/mob/testmob/process(seconds_per_tick)
health -= health_loss_per_second * seconds_per_tick
В первом примере моб теряет по 4 жизни каждый тик сабсистемы, и так как сабсистема мобов процессится каждые 2 секунды, то в итоге моб теряет по 2 жизни в секунду.
Это не учитывает тайминги перед тиком сабсистмы и становится проблемой, если кто-то захочет изменить скорость работы (wait
) сабсистемы мобов. Например, если сабсистема станет запускаться каждую секунду (в два раза быстрее), то и моб начнет терять жизни в 2 раза быстрее: по 4 в секунду вместо 2-х. И наоборот, если сабсистема будет запускаться реже.
Решением является использование seconds_per_tick
. Сабсистема сама сообщает в process()
, как часто она запускается.
Во втором примере мы сделали health_loss
значением в секунду, и ориентировались на переданное сабсистемой значение для высчитывания итогового результата. И не важно, если сабсистема мобов станет запускаться чаще, или медленнее, скорость потери жизней в секунду останется преждней.
Аналогично с prob()
в процессинге:
//Плохо:
/mob/testmob/process()
if(prob(10)) // 10% шанса на событие в тик, когда тик может быть разным
event()
//Хорошо:
/mob/testmob/process(seconds_per_tick)
if(SPT_PROB(10, seconds_per_tick)) // 10% шанса на событие в секунду
event()
SPT_PROB
конвертирует шанс на шанс в секунду и обеспечивает более постоянное значение, зависимое от скорости сабсистемы.
Стоит понимать, что seconds_per_tick
- идеальное "среднее" значение скорости сабсистемы, оно не учитывает возможные лаги игры и замедление работы сабсистем из-за этого.
Не используйте. Причины:
- Первая проблема спавна - вопреки всеобщему мнению, считает в тиках процессора, а не игровых тиках.
- spawn(10), сюрприз-сюрприз вовсе не одна секунда.
- Вторая проблема - ссылки в ассинхронном вызове. Если вы ссылаетесь на объект, который под спавном, и что-то в этом время удалит этот объект(Попытается), то внутри спавна останется ссылка на него.
- Третья проблема - отсутствие возможности профилизировать вызванный под спавном код. В профайлере он будет подписываться как "ASYNC FUNCTION CALL", или что-то подобное, что вообще ничего не говорит о том что это за функция такая.
- Четвёртая проблема - отсутствие контроля над выполнением. В случае с, к примеру, таймером, которые вынесены в отдельную подсистему - можно выбирать приоритет выполнения таймера, в то время как spawn() является низкоуровневым byond-проком, не контролируется нами, и может работать не всегда оптимальным для нас способом.
В зависимости от того, как используется спавн есть два пути замены:
- Если spawn(time):
- Как правило, такие спавны заменяются на
addtimer(CALLBACK(thingtocall, TYPE_PROC_REF(thingtocall_path, proc_name), args), time)
- Если с помощью спавна лишь изменяется переменная датума, то лучше использовать
VARSET_IN(datum, var, var_value, time)
.
- Как правило, такие спавны заменяются на
- Если spawn() или spawn(0):
- Если спавн содержит единственный прок, то просто оберните его в
INVOKE_ASYNC(thingtocall, TYPE_PROC_REF(thingtocall_path, proc_name), args)
. Иначе перенесите всё содержимое спавна в новый прок, который уже и добавите вINVOKE_ASYNC(thingtocall, TYPE_PROC_REF(thingtocall_path, proc_name), args)
- Если всё содержимое прока обернуто в спавн, то в самом проке прописать
set waitfor = FALSE
- Если спавн содержит единственный прок, то просто оберните его в
Примеры замен под спойлерами ниже:
Пример замены spawn(time)
//Плохо:
/mob/some/class/proc/foo()
code
spawn(20)
switch(variable)
if(1)
do_something
if(2)
return
spawn(40)
other_mob.do_something_crazy(a, b)
spawn(30)
name = "Steve"
//Хорошо:
/mob/some/class/proc/foo()
code
addtimer(CALLBACK(src, PROC_REF(do_something_wrapper), variable), 20)
addtimer(CALLBACK(other_mob, TYPE_PROC_REF(/mob, do_something_crazy), a, b), 40)
VARSET_IN(src, name, "Steve", 30)
/mob/some/class/proc/do_something_wrapper(variable)
switch(variable)
if(1)
do_something
if(2)
return
Пример замены spawn(0)
//Плохо:
/mob/some/class/proc/foo()
spawn(0)
switch(variable)
if(1)
do_something
if(2)
return
//Хорошо:
/mob/some/class/proc/foo()
set waitfor = FALSE
switch(variable)
if(1)
do_something
if(2)
return
//Плохо:
/mob/some/class/proc/foo()
code
spawn(0)
switch(variable)
if(1)
do_something
if(2)
return
//Хорошо:
/mob/some/class/proc/foo()
code
INVOKE_ASYNC(src, PROC_REF(do_something_wrapper), variable)
/mob/some/class/proc/do_something_wrapper(variable)
switch(variable)
if(1)
do_something
if(2)
return
//Хорошо:
/mob/some/class/proc/foo()
code
do_something_wrapper(variable)
/mob/some/class/proc/do_something_wrapper(variable)
set waitfor = FALSE
switch(variable)
if(1)
do_something
if(2)
return
Цикл вида for(var/i = 1; i <= const_val; i++)
должен быть записан в следующей форме for(var/i in 1 to const_val)
. Причина: второй цикл работает быстрее. Почему? Хороший вопрос, стоит задать его кодерам BYOND. У обоих видов одинаковое поведение, но у второго есть нюансы. to
тоже самое, что и <=
, соответственно чтобы было <
нужно сделать for(var/i in 1 to const_val - 1)
. Ещё важный момент - const_val
переменная не должна быть изменена во время цикла. То есть если вы делаете проход по списку, который изменяется внутри цикла, а const_val
размер списка, то вам нужно использовать классический, первый, вариант.