Общие правила

  1. Код должен соответствовать нашим правилам, быть простым и понятным;
  2. Код должен быть без ошибок;
  3. Код должен быть написан так, чтобы потом при его изменении не возникло новых ошибок;
  4. Код должен быть написан так, чтобы потом его можно было максимально реиспользовать, как в текущем проекте, так и в будущих;
  5. Код не должен дублировать существующую функциональность, а также содержать дубли внутри себя.

Предупреждения и ошибки

  1. При компиляции не должно быть ни предупреждений, ни тем более ошибок.
  2. Не игнорируйте предупреждения.
  3. Используйте подсказки Resharper с осторожностью, большинстве случаев он предлагает правильные варианты, но иногда они не работают для Unity. Например: длинные, последовательные LINQ выражения.

Организация файлов

  1. Одно пространство имен или один скрипт на файл.
  2. Имя класса и файла должны совпадать.
  3. Расположение файлов должно по возможности соответствовать логической организации сборки.

Синтаксис

  • Блоки кода обрамляются фигурными скобками, причем открывающая и закрывающая скобка должны находиться каждая на своей строке:
    
    private void DoSomething()
    {
    DoSomethingElse();
    }
    
  • В IDE в качестве табуляции должны быть 4 пробела. Это нужно для отображения большего отображения информации на экране.
  • Если после логического оператора if или операторов циклов выполняется только одна строчка кода, то она не обрамляется фигурными скобками, только если эта строчка не новый цикл и(или) логический оператор. Допустимо:
    
    if (isWait)
      i = 0;
     for (int i = 0; i < 10; i++)
      Foo();
    for (int i = 0; i < 10; i++)
    {
      for (int j = 0; j < 10; j++)
      {
        Foo();
      }
    }
    

Не допустимо:



    for (int i=0;i
  • Не нужно делать выравнивание кода табуляцией. Например:
    
    private int     damage = 2;
    private float   health = 3f;
    private string  name   = "name";
    
    В таком стиле минусов больше чем плюсов.

Плюсы:

  • Немного выше читабельность.

Минусы:

  • Меньше ценной информации на экране
  • Затраты по времени (если не настроить IDE)
  • В этом мало смысла, потому что никто не читает код, как книгу. На практике его читают не последовательно.

Комментарии

Код должен быть самодокументируемым!

  • Использовать двойной или тройной слеш. Старайтесь избегать /* … */.
  • Старайтесь описывать рublic, protected и internal определения в виде summary.
  • Предпочтительный язык документации - английский. В крайнем случае - русский. Но не в коем случае не транслит!
  • Если часть кода закомментирована, обязательно пишите, почему код закомментирован, а не просто удален. Для этого используются пометки типа TODO, NOTE и тд. В дальнейшем по ним такой код будет легче обнаружить. Если невозможно сформулировать описание для закомментированного кода, значит он просто не нужен.

Разбиение на #region

  • При реализации абстрактного класса или интерфейса все методы и свойства нужно заносить и в region с именем оного.
  • Если класс содержит в себе больше 1 поля, свойства или метода, или же занимает больше 50 строк кода, то его нужно разбивать на блоки. Названия блоков должны быть следующими:
    • #region Fields - содержит в себе все поля класса
    • #region Properties- содержит в себе свойства метода
    • #region Methods- содержит в себе методы класса
  • Системные методы Unity3D (типа Awake(), Start(), Update()) должны быть вынесены в #region Unity events. Аналогичное требование и к событиям внешних плагинов, например NGUI.

Именование

  • При именовании должны использоваться только буквы латинского алфавита и цифры (стараться их избегать).
  • Не использовать сокращения с удалением гласных типа btn (button) для переменных, свойств и функций класса. Такие сокращение допустимы только для временных переменных в функциях и свойствах.
  • Имена должны быть осмысленны и быть на английском.
  • Используется Camel- и Pascal-тип именования:
  • Локальные переменные и private-переменные класса именуются в стиле Camel с без нижнего подчеркивания, т.е. первая буква слова - строчная, остальные - прописные. Например,maxHealth
  • Файлы, пространства имен, классы, интерфейсы, функции, перечисления, делегаты именуются в стиле Pascal, т.е. все слова с большой буквы, например:MaxHealth
  • Имена констант и элементы перечислений должны быть написаны прописными буквами, слова разделены нижним подчеркиванием. Например: MAX_HEALTH
  • Имена событий должны быть в стиле Pascal и соответствовать тому, что происходит с классом в этот момент. Имена обработчиков этих событий должно начинаться с префикса "On". Например:

 public event Action Initialized = delegate { };
 private void OnInitialized(GameObject go)
 { }

Рекомендации

  • Использовать встроенные типы данных C# вместо их .NET CTS классов-аналогов (int вместо Int32).
  • Названия свойств не должны начинаться с Get и Set.
  • Использовать foreach вместо for-цикла (если нет необходимости в индексе элемента).
  • Старайтесь использовать var, если из правой части выражения понятно значение переменной, а так же в циклах foreach.
  • Не использовать исключения для контроля выполнения программы.
  • Используйте try-catch только в крайних случаях, когда невозможно обработать все возможные ошибки. Например запись в файл.
  • Если класс ни кем не наследуется, то делайте ему приставку sealed. Это сразу защитит от случайно написанных protected, virtual и тд.
  • Проверять события на null прежде чем запускать их. При этом сначала присвоить событие начальной переменной.

private void Init()
{
  var handler = OnInitialized;
  if (handler != null)
    handler(gameObject); 
}
  • Всегда использовать модификаторы доступа, нельзя опускать их!
  • Инициализируйте переменные там же, где их объявили. Не опускайте стандартные значения типа null, 0, 0f и т.д.
  • Все переменные члены должны быть объявлены как private. Общедоступные должны быть преобразованы в свойства.
  • Старайтесь использовать int для всех целых типов.
  • Не используйте магические числа и неименованные константы. Используйте вместо этого перечисления и константы.
  • Не используйте нелокализованные строки в явном виде.
  • Старайтесь использовать readonly, особенно для массивов.
  • Прямые приведения типов запрещены! Используйте вместо этого as и проверку на null.
  • Не используйте оператор is! Он нужен в самых крайних случаях (например, при дессериализации). Использование это оператора означает, что есть какие-то проблемы с архитектурой проекта, неправильно используются основные принципы ООП.
  • Не нужно заменять оператор is оператором as и проверкой на null. Это самообман. Если возникает необходимость в том, что бы узнать конкретный тип объекта, значить стоит задуматься про интерфейс.
  • Используйте String.Format() или StringBuilder при преобразовании и конкатенации строк.
  • Используйте String.Compare и String.IsNullOrEmpty для сравнения строк.
  • Старайтесь избегать рекурсивных методов. Используйте их только в том случае, если цикл не справится с задачей.
  • Избегайте громоздких конструкций в тернарной операции ?:.
  • Старайтесь избегать switch/case. Для малого количества условий старайтесь использовать if/else.
  • Используйте префиксный инкремент (++i) или постфиксный (i++). При этом никогда не делайте i += 1.
  • Не используйте сравнение bool-переменных с true/false. Используйте if (boolVariable) и if (!boolVariable).
  • Программируйте на уровне интерфейсов. Реализацию интерфейса старайтесь делать explicit.

Правила для Unity

  • Избегать public переменных как настроечные данные для редактора. Пока Unity не поддерживает свойства, использовать private-переменные с аттрибутом SerializeField. Все такие переменные должны находиться в одном месте, в начале скрипта.
  • Параметры скрипта-ссылки следует проверять в Awake на null и выдавать в редакторе ошибку или предупреждение, с понятным сообщением о том, что параметр не был настроен.
  • Не используйте нелокализованные строки в явном виде. Вместо этого в скрипте заводится параметр типа string, если строка не локализуема, который уже можно настроить в редакторе на свое усмотрение, в том числе используя шаблоны типа "Some string {0}, {1} " для внесения значений.
  • Если нужно использовать настроечные данные для редактора, то делать свойства для private-переменных и оборачивать их в #ifdef UNITY_EDITOR.

public float ExpirienceForAction
{
  get { return expirienceForAction; }
#if UNITY_EDITOR
  set { expirienceForAction = value; }
#endif
}
  • Все, что платформозависимо, включать в #ifdef. При это не забывайте проверять, что код работает на других платформах.
  • Запрещается использовать Update необоснованно! Update используется, только если нужно постоянно делать какое-то действие (например, перемещать объект). Если нужно сделать какое-то действие по истечении времени, или с каким-то периодом, используйте Coroutine (StartCoroutine, StopCoroutine). Если нужно какое-то действие на событие, используйте event. Они как раз для этого и предназначены.
  • Запрещено использовать SendMessage! Вместо этого определяйте public-функцию и доступ к необходимому объекту(ам) реализовывать через параметры скрипта, вызывающего функцию или event.
  • Предпочтительно включать свои Unity-независимые классы как члены Unity-скрипта, используя расширения.
  • Все классы должны быть в пространствах имен (даже MonoBehaviour, Unity 4.2 это позволяет). Пространства имен должны визуализировать MVC. Например:

namespace MyGame.Data
{
  
}
namespace MyGame.Logic
{
  
}
namespace MyGame.View
{

}
  • Если в компоненте подразумевается обязательное использование другого компонента того же объекта, к классу следует добавить атрибут [RequireComponent(typeof(T))], где T - тип необходимого компонента. Только не забывайте все равно проверять компонент, полученный через GetComponent, на null.
  • Следует писать обязательные комментарии к классу, параметрам скрипта и членам класса с уровнем доступа выше private, если они будут унаследованы.
  • Не забывать об ограничения .NET на MonoTouch. На iOS ведет к JIT-компиляции, которая запрещена.
  • Старайтесь не использовать функции IEnumerable.Sum и IEnumerable.Max. Они не всегда правильно специфицируются в случае AOT-компиляции и не будут определены на момент исполнения. На платформе iOS это обычно приводит к падению. Особенно это касается коллекций из типов-значений. Если вы все же использовали эти функции, обязательно проверьте их работоспособность на устройстве. В ином случае лучше воспользоваться циклом foreach.
  • Для сравнения float-величин не использовать оператор ==. Если происходит сравнение с 0, в большинстве случаев его можно заменить на операторы >=, <=. В остальных случая использовать Unity Mathf.Approximately (последняя не всегда корректно работает на процессорах armv6 w/ VPF, т.к. в них часто аппаратно понижается точность вычислений для их ускорения).

Пример кода скрипта на Unity


using UnityEngine;
 
namespace MyGame.Logic.Controllers
{
  #region Enums
  public enum Directions
  {
    Right,
    Left
  }
  #endregion
  /// summary
  /// Base class to move specified object with some speed.
  /// /summary
  public abstract class MovementController : MonoBehaviour, IMoveable
  {
    #region Fields
    public sealed class MovementData
    {
      private int _acceleration = 0;

      public MovementData(int acceleration)
      {
        _acceleration = acceleration;
      }
    }

    /// 
    /// Send, when controller initialized
    /// 
    public event Action Initialized = delegate { };

    /// summary
    /// Object to move
    /// /summary
    [SerializeField] protected Transform _controlledTransform = null;

    /// summary
    /// Speed of moving
    /// /summary
    [SerializeField] protected float _speed = 360;
    
    private Directions _direction = Directions.Right;    

    private const int SPEED_MULTIPLIER = 2;
    #endregion

    #region Properties

    /// 
    /// Controller's move speed
    /// 
    public virtual float Speed
    {
      get { return _speed; }
      set { _speed = value; }
    }

    /// summary
    /// True if controller is active and can move the object.
    /// /summary
    public abstract bool CanMove { get; }

    #endregion

 
    #region Unity events
 
    protected virtual void Awake()
    {
      if (_controlledTransform == null)
      {
        Debug.LogWarning(string.Format("Field 'controlled transform' on '{0}' wasn't set up in editor. Setting it     automatically", this);
        _controlledTransform = transform;
      }

      var handler = Initialized;
      if (handler != null)
        handler(gameObject);
    }
 
    #endregion

    #region IMoveable
    void IMoveable.Move(Vector3 targetPosition)
    {
      if(CanMove)
      {
        _controlledTransform.Translate((targetPosition - _controlledTransform.position)*SPEED_MULTIPLIER);
      }
    }
    #endregion
  }
}