Правильный стиль оформления кода в программировании

Сообщения
496
Реакции
618
Помог
16 раз(а)
Перед тем как начнём, хочу сказать что эта статья подойдёт для программиста любой квалификации. Если вы начинающий, то статья поможет избежать ряда потенциально вредных привычек. Для продвинутых - избавиться от уже приобретённых, можно хорошо писать код, но непонятно для остальных.
Научиться хорошему стилю в программировании полезно. Автор сам в начале карьеры писал, плохо оформляя код, в итоге ему пришлось со временем избавляться от вредных привычек, что было непросто. Некоторые из рекомендаций 100% верны, другие субъективны и могут быть оспорены. В таких случаях будет даваться несколько вариантов оформления.

Вступительный блок с комментариями

Лучше всего в начале плагина написать комментарии, объясняющие что этот плагин делает. Туда же поместить пояснения к настройкам, авторство, благодарности и.т.п.
Типичный блок примерно таков:
Код:
/*
Copyleft 2008
Plugin thread: http://forums.alliedmods.net/showthread.php?p=633284
DUEL MOD
========

Description
This mod is designed to allow dueling, where players challenge each
other and engage in battle. It is designed for the mod "The Specialists",
but can be used on other mods.

Commands
say /duel - Will challenge the player being looked at to a duel.
say /accept - Will accept a challenge. Note that you can also use /duel,
but you must be looking at the person who challenged you. With /accept,
the last person to challenge you will be accepted.
say /punchingbag - Turns the player into a punching bag
(requires amx_duel_punchingbag to be set to 1).

Credits

Havoc9 Community - Testing help (specifically SaderBiscut and Jimmy Striker).
Lord_Destros - Testing help.
Steely - Testing help.
sawyer - Testing help.
Frost - Testing help.
coderiz - New semiclip method.
Charming - Encouragement.

Changelog:

    Jun 1, 2008 - v1.0 -    Initial release
    Jun 2, 2008 - v1.1 -    [FIXED] Some repeated variables
                [FIXED] Message printing incorrectly
                [FIXED] Duel off not working properly
                [ADDED] Punching bag mode
                [ADDED] True semiclip
                [ADDED] Attack blocking between
                duelists <-> players
                [ADDED] God mode to normal players
    Jun 4, 2008 - v1.2 -    [ADDED] Deny command
                [ADDED] Pair command
                [ADDED] Name parameter to /duel command
                [ADDED] Glow cvar
                [FIXED] Messages printing incorrectly
*/

Плюсы для постороннего человека, открывшего ваше творение:
  • Чётко описаны функционал и тематика
  • Указано для какой игры
  • Расписаны команды. Опционально, можно описать и в теле плагина, но здесь гораздо удобнее для пользователя.
  • Благодарности. Указывайте не только в теме, где постите плагин, но и в исходнике.
  • Информативный лог изменений дабы пользователи знали чего ждать от какой версии
Copyleft необязателен, однако не лишним будет напомнить: плагины AMXX распространяются под открытой лицензией GPL.
Наиболее важным в блоке комментариев является подача информации пользователю, как именно её расписать - дело ваше.

Макросы и препроцессор

Данная секция включает в себя заголовки включений (a.k.a. "#include") и определения компилятора (a.k.a. "#define"). Здесь мало что можно испортить, но важно помнить, что все макросы должны быть прописаны заглавными буквами. Пример:
Код:
#define MAX_ITEMS 10
смотрится гораздо приятнее нежели
Код:
#define max_items 10
Подобный стиль позволяет нам избежать ошибки принятия макросов за переменные и является общепризнанным. Исключение только одно:
Код:
#define MyNewIsUserAlive(%1) is_user_alive( %1 )
Подобное приемлемо, потому что макрос по существу действует как функция, а не просто хранит данные в памяти.
Не стоит подключать лишнее. Например, если не используете функции модуля FakeMeta, то не надо добавлять fakemeta.inc.

Код в теле условия следует табулировать(проставлять отступы). Будет видно где что находится.
Код:
PunishUser( id )
{
#if defined KILL_USER
    user_kill( id )
    client_print( 0, print_chat, "[Kill] The user has been killed")
#else // KILL_USER
    user_slap( id, 0 )
    client_print( 0, print_chat, "[Kill] The user has been slapped")
#endif // KILL_USER
}
Некоторые программисты ставят пробелы и перед макросами. Оба варианта приемлемы, но автор считает, что перед макросами пробел лучше не ставить, так как можно визуально потерять тело условия.
Также правильным является оставление комментариев к каждому #else, #elseif и #endif.

Отступы, табуляция и интервалы

Табуляция должна делаться каждый раз, когда Вы открываете блок кода (в деталях - чуть позже). Обычно табуляция проставляется при помощи кнопки "Tab" либо 4 пробелов. Реже используют 1, 3 или 8 пробелов. Чаще встречается именно "Tab" потому что удобнее в текстовых редакторах.
Насчитывается несколько стилей интервалов, но автору кажется лучшим добавлять пробел:
  • при взятии параметра в скобки
  • сразу после условия и ключевого слова
  • до и после каждого оператора с 2 параметрами
  • После запятой
Код:
if ( !cmd_access( id, level, cid ) )
Код:
for ( new i; i < 10; i++ )
Это не является стандартом и вполне обсуждаемо, но логично для читабельности кода. Также имеет смысл добавление 2 переводов каретки вместо одного между функциями.

Регистр

Важно для переменных и функций. Эта глава неоднозначна и может быть оспорена, однако автор верит что стиль написания "camel case" наиболее предпочтителен.("camel case" - дословно "верблюжий регистр". Здесь автор имеет в виду двугорбых верблюдов в качестве примера перемежающихся букв верхнего и нижнего регистров. Например, сокращение "СортСемОвощ", - прим.переводчика)
Ниже представлен упрощённый вариант "camel case", называемый "mixed case"("смешанный регистр"), автору такой стиль нравится больше.
В именах функций первую буква каждого слова делайте заглавной
Код:
public EventDeathMsg()
С переменными почти так же, но заглавной пишется первая буква каждого слова после первого.
Код:
new gMyVariable
new myVariable
new userKills
В полном варианте "camel case" следует с большой буквы писать каждую первую букву каждого слова, независимо от того, переменная перед нами или нет.
Код:
new MyVariable
new UserKills
Проблема в том, что подобная запись не позволяет с ходу отличить локальные переменные от функций. Уж больно похоже называются.

Аналогом можно считать нижнее подчёркивание вместо слитного написания, однако согласитесь, что выглядит несколько странно
Код:
new My_Variable
Хотя и жизнеспособно. Используйте одно из двух - camel/mixed case или подчёркивание, но не смешивайте их.
Как отмечено ранее, макрос всегда пишется в верхнем регистре.

Глобальные переменные и Префиксы типов

Много ошибок может быть из-за незнания что что-то глобально. Все глобальные переменные должны быть обозначены буквой g.
Код:
new gMyVariable
Либо через префикс g_
Код:
new g_MyVariable
Каждую такую переменную следует писать с новой строки и с добавлением перевода каретки для облегчения поиска
Код:
new gMyVariable
new gMyOtherVariable
приятнее глазу, нежели
Код:
new gMyVariable, gMyOtherVariable
Многие помечают название переменной в зависимости от её типа(так называемая Венгерская запись или HN). Сейчас от подобного отходят, однако вполне может встретиться.
  • g or g_ - глобальные переменные (global)
  • p or p_ - указатели (Pointer. Приемлемо. А вот обозначений ниже лучше избегать)
  • i - целое число (integer)
  • f or fl - число с плавающей точкой (float)
  • sz - строковые переменные (string)
  • b - логические типы (boolean)
  • h - дескрипторы (handlers. Могут использоваться вместо тэга указателя, что технически не является ошибкой)
  • v - векторы (Vector. Не стандарт, но всё ещё полезно). К примеру, такая запись векторов/ координат new Float: g_fvOrigin[3];
  • fn - функции (Fubction. устарело даже по меркам HN)
Признаков больше, выше перечислены только используемые в Pawn. Примеры Венгерской записи:
Код:
// глобальная переменная
new g_iKills

// глобальная переменная типа float
new Float:g_flSpeed

// логическая переменная
new bool:bFlag

// функция
public fnEventResetHUD()

// указатель
new g_pCvar
Если вы не пользуетесь HN, автор советует применять "g" вместо "g_".
С точки зрения технического анализа Венгерская запись актуальна только для указателей и глобальных переменных. Pawn не содержит типов и поэтому не коррелирует с к актуальными типами данных. Префикс для Boolean бессмысленен, типы float и cell конвертируются виртуальной машиной AMXX при надобности.

Блоки кода

Такие блоки отделяются фигурными скобками. Не всегда, есть ситуации, когда фигурные скобки могут быть опущены, но автор предпочитает использовать во всех случаях ради унификации.
Обратимся к интервалам. Существует 2 способа их проставления: с новой строки и в той же.
Код:
MyFunction()
{
либо
Код:
MyFunction() {
Первый удобнее, так как закрывающая скобка расположена вертикально над открывающей, мы можем визуально провести линию сверху вниз и понять какой именно блок выделен.

Есть несколько ситуаций, когда скобки всё же можно игнорировать.
  • В блоке только один вызов функции или одна операция. Например, log_amx( "%d", get_user_kills( id ) )
  • There is a single code block inside it, even if it contains a lot more code that does not fit in a single function call.
1.
Код:
MyFunction( id )
    user_kill( id )
2.
Код:
MyFunction( id )
    if( is_user_alive( id ) )
    {
        client_print( 0, print_chat, "We are now killing %d", id )
        user_kill( id )
    }
Оба способа жизнеспособны и не вызывают проблем при компиляции.
В целом отбрасывание скобок в исключениях субъективно. Находится масса аргументов за и против. С одной стороны, пишем меньше кода. С другой, код менее читабелен. Временами встречается промежуточное решение - табуляция вместо скобок. Так или иначе, все эти варианты приемлемы.

Комментирование

Оставление комментариев - важная составляющая хорошего стиля.
Большие тексты вне функций должны быть закомментированы с использованием операторов "/*" и "*/" , первый открывает блок, второй закрывает.
В случае однострочных комментариев используйте знак // . Учтите, что Pawn не поддерживает вложенные комментарии, то есть вот так делать не надо
Код:
/*
    this is the first comment
    /* this is a nested comment */
                                               */
Обратите внимание на цвет последнего "*/". Интерпритатор не понимает что мы от него хочем.
Если не обойтись без вложенных комментов, то это можно сделать при помощи препроцессора #if с условием 0
Код:
#if 0
    первый
#if 0
        вложенный
#endif
#endif
Выглядит не очень и больше подходит для ситуаций когда надо что-то быстро проверить/отладить.

Старайтесь, чтобы длина любой строки не превышала 80 символов, не у всех широкоформатные мониторы. К тому же слишком большую длину может жаловаться компилятор. Некоторые добавляют в начало комментария 1-2 пробела для повышения читабельности.
Комментарий должен быть максимально нагляден и отражать суть: что делается и каков ожидается результат.
Например, вот этот бесполезен, так как из его содержания ничего не понять
Код:
// Start the for loop.
for ( new i; i < 7; i++ )
Гораздо лучше описать следующим образом:
Код:
// There's a strange memory corruption bug in this function.
//  As such, we have to loop through each of the 7 elements
//  in the array and set them to the value that they should be.
for ( new i; i < 7; i++ )
Комментарии следует размещать также между большими отдельными блоками программы. Например, в форме заголовков или мини-описаний
Код:
//////////////////////////////////
// HELPER FUNCTIONS             //
//////////////////////////////////
Разделители

Пробелы и разделители имеют большое значение в программировании, так как позволяют лучше понять код. Существует много различных стилей, автор предпочитает добавлять разделитель для отделения общих по смыслу частей функций.
Код:
// Ignore the fact that this function would bear no real
//  useful results when actually used in a server.
MyFunction()
{
    new id = random_num( 1, 13 )
    user_kill( id )

    set_pcvar_num( p_MyCvar, 9 )
}
set_pcvar_num, устанавливающий значение, имеет мало что общего с нахождением игрока и убийством, поэтому отделён.

Переменные и функции

Как показывает опыт, имена переменных и функций должны быть максимально описательными. Ниже пример плохого имени
Код:
new temp
Ясно одно, это нечто временное, но непонятно для чего используется
Сравните с таким
Код:
new KillsSinceLastSpawn
Название говорит нам всё что нужно знать. Можно даже предположить что переменная целочисленного типа.

Желательно проставлять тэги, например "Event" у события или "Forward" у форварда.
Код:
register_forward( FM_Spawn, "ForwardSpawn" )
register_event( "DeathMsg", "EventDeathMsg", "a" )
Часто вы увидите краткую пометку, то есть не "Forward", а "Fwd", так тоже можно. Важно отделить подобные функции от остальных служебных.

Дополнительно

Здесь то, что по смыслу не вошло в предыдущие главы.

По возможности пользуйтесь константами
Код:
return PLUGIN_CONTINUE
нагляднее, нежели
Код:
return 0

Знак ";" полезен если вы планируете переходить с Pawn на другой язык(C/C++/PHP), но часто допускаете ошибки в его использовании. Например, многие не знают, что в конце цикла "do...while" должна стоять одна ";" . Опытному программисту не составит труда переключаться между наличием и отсутствием ";" . Так как Pawn позволяет не использовать ";" , то нет смысла проставлять лишний раз. Заставить компилятор требовать обязательного расставления ";" в конце строк можно директивой
Код:
#pragma semicolon 1

Иногда, при записи API или заголовка, вам придётся документировать свои функции таким образом, чтобы другие могли их понять. Doxygen (исходный код, документирующий пакет программного обеспечения) использует определенный формат, который AMXX начал использовать после добавления SQLx. Вероятно, это является лучшим способом, потому что такая запись наиболее читабельна. Вот пример Doxygen-задокументированной служебной функции:
Код:
/**
* Print command activity.
* @param id           Calling admin's id
* @param key[]      Language key
* @param any:...    Arguments to the format function (w/out the id/lang key)
* @return    True on success, False on fail
**/

stock UTIL_PrintActivity(const id, const szLangKey[],any:...)
В совместных проектах имеет смысл указывать автора конкретной части кода
Код:
// Hawk552: Added this function to gather and store player
//  names, then sort them alphabetically.
SortNames( Names[][], NumNames, MaxLength )
Заключение

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

Уточнения от сообщества Dev-CS

1. Касательно "camel case"/"mixed case" в регистрах.

Спорно. Как правило там, где нету явного указания области видимости, различают именно по первой букве. Если большая то переменная глобальная, иначе локальная. В python-е принято обозначать приватные свойства через "_" в начале.

2. На скорость выполнения кода наличие или отсутствие скобок и комментариев никак не влияет, поэтому в первую очередь думайте о читабельности.

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

4. Автор пишет, что не стоит в конце каждой строки проставлять знак ";". Так говорили в начале 2000-ых касательно JavaScript. В итоге понадобились годы дабы убедить всех использовать везде ";". То, что компилятор AMXX сам их проставляет вовсе не означает, что он не может ошибиться. Потому лучше добавлять, благо наличие либо отсутствие ";" никак не сказываеться на производительности.


Автор оригинальной статьи Hawk552
Плагин, соответствующий хорошему стилю, размещён на AlliedMods.
Отдельное спасибо fantom за критику и современные реалии.
Все права на перевод принадлежат Dev-CS.ru TEAM. При копировании материала активная ссылка обязательна.
 
Последнее редактирование:
Сообщения
496
Реакции
618
Помог
16 раз(а)
Перемещено в общий раздел.
 
Сообщения
2,713
Реакции
2,995
Помог
59 раз(а)
Сейчас от подобного отходят, однако вполне может встретиться.
в более продвинутых языках программирования где используется IDE по типу Microsoft Visual Studio обычно венгерская нотация не используется, по сколько в IDE есть подсветки, и обозначение типа объявленной переменной.
В нашем же случае с AMXX PAWN подсветки типа переменной не встречал нигде почти, в следствии этого используем её.
Код:
new Float: g_fVariable; // Новая переменная типа Float.
9 Фев 2018
b - логические типы
->
b - логические типы (булевые)
9 Фев 2018
h - дескрипторы (могут использоваться вместо тэга указателя, что технически не является ошибкой)
-> дескрипторы, хэндлеры
9 Фев 2018
векторы (нестандарт, но всё ещё полезно)
используется обычно для записи координат или векторов.
к примеру, new Float: g_fvOrigin[3];
 
Сообщения
207
Реакции
420
Помог
10 раз(а)
Ещё, возможно, стоит добавить такой пункт, как "разбиение поставленной задачи на задачи поменьше".

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

Пользователи, просматривающие эту тему

Сейчас на форуме нет ни одного пользователя.
Сверху Снизу