Войти
UDKСтатьи

Стандарты программирования

Внимание! Этот документ ещё не опубликован.

Автор:

Стандарты программирования

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

Соглашения о программировании являются важными для программистов по ряду причин:

Организация классов: имена классов - существительные

Организация состояний: имена состояний - прилагательные

Организация методов: имена методов - глаголы

Переменные: используются соответствующие типы C++!

Комментарии

Несколько советов о комментировании кода (из Kernighan & Pike The Practice of Programming):

Плохой пример Хороший пример
t = s + l - b; TotalLeaves =  SmallLeaves + LargeLeaves  - SmallAndLargeLeaves;
Плохой пример Хороший пример
// инкремент iLeaves // мы знаем, что есть еще один чайный лист
Leaves++; Leaves++;
Плохой пример Хороший пример
// общее количество листьев равно сумме  
// малых и больших листьев за минусом  
// числа малых и больших листьев  
t = s + l - b; TotalLeaves = SmallLeaves + LargeLeaves - SmallAndLargeLeaves;
Плохой пример Хороший пример
// инкремент iLeaves не осуществится! // мы знаем, что есть еще один чайный лист
Leaves++; Leaves++;

Наш стиль документирования кода основан на Javadoc External Link | Стандарты программирования (за исключением проектов на C#). В итоге мы получаем автоматически сгенерированную документацию.

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

class Tea extends WarmBeverage
   native, perobjectconfig, transient;
/**
 * This class does lots of neat things.  It works
 * with a few other classes to do other neat things.
 */

/** This stores the value of tea in china. */
var float Price;
  

/**
 * state Brewing
 * This state was created to represent Brewing in the cup.
 * entered: AddBoilingWater is called in the default state.
 * exited:  through Pour or too much time elapses (goes to 
 * GettingBitter)
 */
state Brewing 
{

   /**
    * Brewing::Steep calculates a delta-taste value for the tea given the 
    * volume and temperature of water used to steep.
    *
    * @param    VolumeOfWater - amount of water used to brew
    *           a positive number of milliliters (not checked!)
    * 
    * @param    TemperatureOfWater - water's temperature in 
    *           degrees Kelvin. 273 < temperatureOfWater < 380
    *
    * @param    NewPotency - the tea's potency after steeping starts;
    *           should be between 0.97 and 1.04
    *
    * @return    returns the change in intensity of the tea in tea taste
    *           units (TTU) per minute
    *
    */
   
   function float Steep (float VolumeOfWater, float TemperatureOfWater, 
                         out float NewPotency)
   {
   }
}

Что должен включать в себя комментарий к классу?


Что должен включать в себя комментарий к состоянию?
Какие части функции должны быть прокомментированы?

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

//@debug    отладочный код, который не будет включен в релиз <br>
//@todo     пояснение, что в этом месте будет помещен необходимый код
Обратите внимание, что перед релизом отладочный код из движка должен быть исключен. Если вам необходимо оставить его в файлах исходного кода, то он должен быть заключен в блоки if(0), а не  в #if 0. В этом случае код будет скомпилирован, но выброшен при оптимизации. Включите соответствующий комментарий, поясняющий, что это отладочный код, укажите свое имя и назначение отладочного кода.

Модификация кода сторонних разработчиков:

При каждой модификации исходного кода библиотек сторонних разработчиков не забудьте пометить свои изменения комментарием //@[Название вашей организации], а также поясните причину внесения изменений. Это облегчит слияние вашего кода с новыми версиями используемых вами библиотек.

Дополнительные стандарты для C#

При документировании кода на C# вместо комментариев вида /** */ применяется формат ///. Например:

namespace UnrealLogging
{
   /// <summary>
   /// Indicates the level of the message being logged
   /// </summary>
   public enum LogLevel { LOG_Error, LOG_Warning, LOG_Debug };

   /// <summary>
   /// Generic logging interface. This interface is to be implemented to
   /// provide logging support for whichever the target environment is.
   /// </summary>
   public interface ILogger
   {
      /// <summary>
      /// Writes the message to the implemented logging support
      /// </summary>
      /// <param name="MsgLevel">The severity of the message being written</param>
      /// <param name="StrMsg">The message to write out</param>
      void Log(LogLevel MsgLevel,string StrMsg);

      /// <summary>
      /// Sets the filtering that will be applied to logging. No message
      /// with a level higher than what is set in here will get logged.
      /// </summary>
      /// <param name="MaxLevel">Maximum log level to write out</param>
      void SetFilter(LogLevel MaxLevel);
   }
}

Необходимые настройки для проекта

Включите генерацию файлов XML-документации и всегда держите их в актаульном состоянии.

Включите опцию, генерирующую ошибку при отсутствии документации к коду.

Соглашения по именованию

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

Сразу после объявления переменной желательно написать к ней пояснительный комментарий. Кроме того, это требует стиль Javadoc. Вы можете писать комментарии как к отдельным переменным, так и группам переменных, расположенных в нескольких строках кода, идущих подряд. Отступы между строками не обязательны.

Для именования переменных используйте следующий формат:  ThisIsAGoodVariableName. Не применяйте форматы: "thisIsABadName" или "this_is_a_bad_name".

Имена всех переменных булевого типа, а также имена функций, возвращающих булево значение, должны быть вопросами. Перед именами переменных булевого типа ставьте префикс b.

/** the tea weight in kilograms */
var float TeaWeight;

/** the number of tea leaves */
var int TeaNumber;

/** TRUE indicates tea is smelly */
var bool bDoesTeaStink;

/** non-human-readable FName for tea */
var name TeaName;

/** human-readable name of the tea */
var String TeaName; 
К концу имен структурированных типов добавляйте имя класса:
/** Which class of tea to use */
var class<Tea> TeaClass;

/** The sound of pouring tea */
var Sound TeaSound;

/** a picture of tea */
var Texture TeaTexture;

Для классов C++ все переменные по умолчанию должны быть закрытыми (private). Делайте их общедоступными (public) только в случаях, когда необходимо взаимодействие кода на C++ со сценариями, то есть для кода, генерированного в XxxClasses.h.

Для именования процедур (функций без возвращаемого значения) вы должны использовать только глаголы, располагая их перед именами объектов. Исключением являются случаи, когда процедура вызывается из самого объекта. Во избежание неоднозначности при именовании процедур не используйте слова "Handle" и "Process".

Tea SomeT;
SteepTea(SomeT);    // method names type of object processed
SomeT.Pour();       // method invoked on Tea; verb is enough

Имена функции, возвращающих значение, должны описывать возвращаемое значение; имя должно дать ясно понять, какое значение вернет функция. Это особенно важно для булевых функций. Рассмотрим следующие два метода:

function bool CheckTea(Tea t) {...} // what does TRUE mean?
function bool IsTeaFresh(Tea t) {...} // name makes it clear TRUE means tea is fresh
Имена состояний и классов для удобства чтения должны начинаться с заглавной буквы. Имена классов должны быть существительными, а имена состояний -  описывать состояние (например, прилагательным). Обратите внимание, что состояние никогда не используется вне класса, то есть всегда есть неявный объект.

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

void Update(FLOAT DeltaTime, UBOOL /*bForceUpdate*/);

В сценариях Unreal Script для параметров, передаваемых по ссылке, используйте префикс out_:

function PassRefs (out float out_wt, float ht, out int out_x)

Константы

При объявлении констант C++ старайтесь вместо директив компилятора применять переменные со спецификатором const.

Виртуальные функции

В производном классе при объявлении виртуальной функции, переопределяющей виртуальную функцию в родительского класса, обязательно используйте ключевое слово virtual. Хотя согласно дополнительному стандарту С++ спецификатор "virtual" наследуется, код намного яснее при явном указании ключевого слова virtual.

Исполняемые блоки

Фигурные скобки { }

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

блоки if - else

При использовании if - else заключайте все исполняемые блоки в фигурные скобки. Это предотвращает возможные ошибки. Если не использовать скобки, то кто-нибудь может невольно добавить еще одну строку в блок if - else, тем самым создав трудно локализуемую ошибку исполнения. Всегда используйте фигурные скобки!

if (bHaveUnrealLicense)
{
   InsertYourGameHere();
}
else
{
   CallMarkRein();
}
При необходимости осуществления множества проверок используйте выражение else if. Это улучшит читаемость кода.
if (TannicAcid < 10)
{
   log("Low Acid");
}
else if (TannicAcid < 100)
{
   log("Medium Acid");
}
else
{
   log("High Acid");
}

Отступы

При наборе кода в пределах исполняемых блоков делайте отступы. Для самих классов UnrealScript отступов делать не нужно. Для создания отступов используйте знаки табуляции, а не пробелы.

Блоки switch

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

К каждому блоку switch добавляйте действие по умолчанию (default:), даже в тех случаях, когда действие по умолчанию не требует выполнения каких либо операций. Возможно необходимость в них появится позже.

switch (condition)
{ 
   case 1: 
      --code--;
      // falls through
   case 2:
      --code--;
      break;
   case 3:
      --code--;
      return;
   case 4:
   case 5:
      --code--;
      break;
   default:
      break;
}

Блок defaultproperties

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

defaultproperties
{
   // variables inherited from Object
   bGraphicsHacks=TRUE
   
   // variables inherited from WarmBeverage
   bThirsty=FALSE

   // class variables
   bTeaDrinker=TRUE
}

Булевы выражения

В коде на C++ в качестве значений логических выражений всегда используйте TRUE или FALSE. Не используйте true или false, которые могут вызвать сумасшедшие проблемы при компиляции. Не используйте 0 или 1, назначение подобных выражений будет неочевидным.

В коде сценариев UnrealScript вы должны использовать только true and false (в нижнем регистре).

Не бойтесь определить несколько локальных булевых переменных, чтобы сделать код более читабельным. Во втором примере, приведенном ниже, имена переменных делают причину вызова функции DoSomething() более понятной.

Фрагмент кода:

if ((Blah.BlahP.WindowExists.Etc && stuff) && 
    !(Player exist && Gamestarted && player still has pawn &&
    IsTuesday())))
{
   DoSomething();
}
должен быть заменен на:
local bool bLegalWindow;
local bool bPlayerDead;

bLegalWindow = (Blah.BlahP.WindowExists.Etc && stuff);
bPlayerDead = (Player exist && Gamestarted && 
               player still has pawn && IsTuesday());

if ( bLegalWindow && !bPlayerDead ) 
{
   DoSomething();
}
Обратите внимание: выражение, присваиваемое переменной bPlayerDead, разбито на две строки, а вторая срока этого выражения выровнена по отношению к первой.

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

Общие вопросы стиля:

Страницы: 1 2 Следующая »

#UDK, #Unreal Development Kit

12 мая 2011