Diretrizes de codificação

Este documento descreve as diretrizes de codificação recomendadas do World Locking Tools para Unity. A maioria dessas sugestões segue os padrões recomendados do MSDN.


Cabeçalhos de informações de licença de script

Todos os scripts postados no World Locking Tools para o Unity devem ter o cabeçalho de licença padrão anexado, exatamente conforme mostrado abaixo:

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

Todos os arquivos de script enviados sem o cabeçalho de licença serão rejeitados.

Cabeçalhos de resumo de função/método

Todas as classes, structs, enumerações, funções, propriedades e campos públicos postados devem ter sua finalidade e uso descritos, exatamente conforme mostrado abaixo:

    /// <summary>
    /// The Controller definition defines the Controller as defined by the SDK / Unity.
    /// </summary>
    public struct Controller
    {
        /// <summary>
        /// The ID assigned to the Controller
        /// </summary>
        public string ID;
    }

Essa regra garante que a documentação seja gerada e divulgada adequadamente para todas as classes, métodos e propriedades.

Todos os arquivos de script enviados sem marcas de resumo adequadas serão rejeitados.

Regras de namespace

Todas as classes e extensões devem ter o escopo definido pelo namespace, selecionado adequadamente dos namespaces abaixo.

Microsoft.MixedReality.WorldLocking.Core – código básico que atende ao serviço básico do World Locking Tools.

Microsoft.MixedReality.WorldLocking.Tools – recursos opcionais que complementam o desenvolvimento com o World Locking Tools. As visualizações de diagnóstico e implementações de linha de base de manipuladores de eventos de aplicativo são alguns exemplos.

Microsoft.MixedReality.WorldLocking.Examples – implementações específicas que demonstram como usar recursos do World Locking Tools e os benefícios obtidos.

Os recursos relacionados em um dos namespaces acima podem ser agrupados estendendo a um novo namespace secundário.

O que fazer

namespace Microsoft.MixedReality.WorldLocking.Examples.Placement
{
    // Interface, class or data type definition.
}

A omissão do namespace para uma interface, classe ou tipo de dados fará com que a alteração seja bloqueada.

Espaços versus Guias

Certifique-se de usar quatro espaços em vez de guias ao contribuir com este projeto.

Além disso, certifique-se de que os espaços sejam adicionados para funções condicionais/loop como if/while/for

O que não fazer

private Foo () // < - space between Foo and ()
{
    if(Bar==null) // <- no space between if and ()
    {
        DoThing();
    }
    
    while(true) // <- no space between while and ()
    {
        Do();
    }
}

O que fazer

private Foo()
{
   if (Bar==null)
   {
       DoThing();
   }
   
   while (true)
   {
       Do();
   }
}

Espaçamento

Não adicione espaços entre colchetes e parênteses:

O que não fazer

private Foo()
{
    int[ ] var = new int [ 9 ];
    Vector2 vector = new Vector2 ( 0f, 10f );
}

O que fazer

private Foo()
{
    int[] var = new int[9];
    Vector2 vector = new Vector2(0f, 10f);
}

Convenções de nomenclatura

Sempre use PascalCase para propriedades públicas/protegidas/virtuais e camelCase para propriedades e campos privados.

As estruturas de dados que exigem que os campos sejam serializados por JsonUtility são a única exceção.

O que não fazer

public string myProperty; // <- Starts with a lower case letter
private string MyProperty; // <- Starts with an uppercase case letter

O que fazer

public string MyProperty;
protected string MyProperty;
private string myProperty;

Modificadores de acesso

Sempre declare um modificador de acesso para todos os campos, propriedades e métodos.

Todos os métodos de API do Unity devem ser private por padrão, a menos seja necessário substituí-los em uma classe derivada. Nesse caso, protected deve ser usado.

Os campos devem ser sempre private, com public ou protected acessadores de propriedade.

O que não fazer

// protected field should be private
protected int myVariable = 0;

// property should have protected setter
public int MyVariable { get { return myVariable; } }

// No public / private access modifiers
void Foo() { }
void Bar() { }

O que fazer

public int MyVariable { get; protected set; } = 0;

private void Foo() { }
public void Bar() { }
protected virtual void FooBar() { }

Usar chaves

Sempre use chaves após cada bloco de instrução e coloque-as na linha seguinte.

Não

private Foo()
{
    if (Bar==null) // <- missing braces surrounding if action
        DoThing();
    else
        DoTheOtherThing();
}

O que não fazer

private Foo() { // <- Open bracket on same line
    if (Bar==null) DoThing(); <- if action on same line with no surrounding brackets 
    else DoTheOtherThing();
}

O que fazer

private Foo()
{
    if (Bar==true)
    {
        DoThing();
    }
    else
    {
        DoTheOtherThing();
    }
}

Classes, structs e enumerações públicas devem ficar em seus próprios arquivos.

Se a classe, struct ou enumeração puder ser privada, ela pode ser incluída no mesmo arquivo. Essa inclusão evita problemas de compilação com o Unity e garante a abstração de código adequada. Também reduz conflitos e alterações significativas quando o código precisa ser alterado.

O que não fazer

public class MyClass
{
    public struct MyStruct() { }
    public enum MyEnumType() { }
    public class MyNestedClass() { }
}

O que fazer

// Private references for use inside the class only
public class MyClass
{
   private struct MyStruct() { }
   private enum MyEnumType() { }
   private class MyNestedClass() { }
}

O que fazer

MyStruct.cs

// Public Struct / Enum definitions for use in your class.  Try to make them generic for reuse.
public struct MyStruct
{
   public string Var1;
   public string Var2;
}

MyEnumType.cs

public enum MuEnumType
{
    Value1,
    Value2 // <- note, no "," on last value to denote end of list.
}

MyClass.cs

public class MyClass
{
    private MyStruct myStructreference;
    private MyEnumType myEnumReference;
}

Ordenar enumerações para a extensão adequada.

Se for provável que uma enumeração seja estendida no futuro, é fundamentar ordenar padrões na parte superior da enumeração. Essa ordenação garante que os índices de enumerações não sejam afetados pelas inclusões.

O que não fazer

public enum SDKType
{
    WindowsMR,
    OpenVR,
    OpenXR,
    None, <- default value not at start
    Other <- anonymous value left to end of enum
}

O que fazer

   /// <summary>
   /// The SDKType lists the VR SDK's that are supported by the MRTK
   /// Initially, this lists proposed SDK's, not all may be implemented at this time (please see ReleaseNotes for more details)
   /// </summary>
   public enum SDKType
   {
       /// <summary>
       /// No specified type or Standalone / non-VR type
       /// </summary>
       None = 0,
       /// <summary>
       /// Undefined SDK.
       /// </summary>
       Other,
       /// <summary>
       /// The Windows 10 Mixed reality SDK provided by the Universal Windows Platform (UWP), for Immersive MR headsets and HoloLens. 
       /// </summary>
       WindowsMR,
       /// <summary>
       /// The OpenVR platform provided by Unity (does not support the downloadable SteamVR SDK).
       /// </summary>
       OpenVR,
       /// <summary>
       /// The OpenXR platform. SDK to be determined once released.
       /// </summary>
       OpenXR
   }

Encerrar os nomes das enumerações com "Tipo"

Os nomes das enumerações devem indicar claramente sua natureza através do sufixo Tipo.

O que não fazer

public enum Ordering
{
    First,
    Second,
    Third
}
public enum OrderingEnum
{
    First,
    Second,
    Third
}

O que fazer

public enum OrderingType
{
    First = 0,
    Second,
    Third
}

Revisar o uso de enumerações para Bitfields

Se houver a possibilidade de um enum exigir vários estados como um valor, por exemplo, Handedness = Left & Right. A enumeração precisa ser decorada com BitFlags para ser usada corretamente

O arquivo Handedness.cs possui uma implementação concreta para esse caso

O que não fazer

public enum Handedness
{
    None,
    Left,
    Right
}

O que fazer

[flags]
public enum HandednessType
{
   None = 0 << 0,
   Left = 1 << 0,
   Right = 1 << 1,
   Both = Left | Right
}

Práticas recomendadas, incluindo recomendações do Unity

Algumas plataformas de destino deste projeto precisam levar o desempenho em consideração. Neste sentido, sempre tenha cuidado ao alocar memória no código chamado com frequência em algoritmos ou loops de atualização restritos.

Encapsulamento

Sempre use campos privados e propriedades públicas se o acesso ao campo for necessário de fora da classe ou struct. Colocalize o campo privado e a propriedade pública. A localização facilita a visualização do suporte da propriedade e se o campo pode ser modificado pelo script.

Se a capacidade de editar o campo no inspetor for necessária, é uma prática recomendada seguir as regras de Encapsulamento e serializar o campo de suporte.

As estruturas de dados que exigem que os campos sejam serializados pelo JsonUtility são a única exceção a essa regra, onde uma classe de dados precisa ter todos os campos públicos para que a serialização funcione.

O que não fazer

public float MyValue;

O que fazer

// private field, only accessible within script (field is not serialized in Unity)
private float myValue;

Fazer

// Enable private field to be configurable only in editor (field is correctly serialized in Unity)
[SerializeField] 
private float myValue;

Não

private float myValue1;
private float myValue2;

public float MyValue1
{
    get{ return myValue1; }
    set{ myValue1 = value }
}

public float MyValue2
{
    get{ return myValue2; }
    set{ myValue2 = value }
}

O que fazer

// Enable field to be configurable in the editor and available externally to other scripts (field is correctly serialized in Unity)
[SerializeField]
[ToolTip("If using a tooltip, the text should match the public property's summary documentation, if appropriate.")]
private float myValue; // <- Notice we co-located the backing field above our corresponding property.

/// <summary>
/// If using a tooltip, the text should match the public property's summary documentation, if appropriate.
/// </summary>
public float MyValue
{
    get{ return myValue; }
    set{ myValue = value }
}

Use for em vez de foreach quando possível

Em alguns casos, um foreach é necessário, por exemplo, ao executar um loop em um IEnumerable. Mas, para benefício de desempenho, evite foreach quando possível.

O que não fazer

foreach(var item in items)

O que fazer

int length = items.length; // cache reference to list/array length
for(int i=0; i < length; i++)

Armazene os valores em cache e os serialize na cena/pré-fabricação sempre que possível.

Com o HoloLens em mente, é melhor otimizar as referências de desempenho e cache na cena ou pré-fabricação para limitar as alocações de memória de runtime.

O que não fazer

void Update()
{
    gameObject.GetComponent<Renderer>().Foo(Bar);
}

O que fazer

[SerializeField] // To enable setting the reference in the inspector.
private Renderer myRenderer;

private void Awake()
{
   // If you didn't set it in the inspector, then we cache it on awake.
   if (myRenderer == null)
   {
       myRenderer = gameObject.GetComponent<Renderer>();
   }
}

private void Update()
{
   myRenderer.Foo(Bar);
}

O cache faz referência a materiais, não chame ".material" sempre.

O Unity criará um novo material sempre que você usar ".material", o que causará uma perda de memória se não for limpo corretamente.

O que não fazer

public class MyClass
{
    void Update() 
    {
        Material myMaterial = GetComponent<Renderer>().material;
        myMaterial.SetColor("_Color", Color.White);
    }
}

O que fazer

// Private references for use inside the class only
public class MyClass
{
   private Material cachedMaterial;

   private void Awake()
   {
       cachedMaterial = GetComponent<Renderer>().material;
   }

   void Update() 
   {
       cachedMaterial.SetColor("_Color", Color.White);
   }
   
   private void OnDestroy()
   {
       Destroy(cachedMaterial);
   }
}

Como alternativa, use a propriedade "SharedMaterial" do Unity que não cria um novo material sempre que ele é referenciado.

Use a compilação dependente de plataforma para garantir que o kit de ferramentas não interrompa a compilação em outra plataforma

  • Use WINDOWS_UWP para usar APIs não Unity específicas da UWP. Essa definição impedirá que eles tentarem ser executados no Editor ou em plataformas sem suporte. Essa definição é equivalente a UNITY_WSA && !UNITY_EDITOR e deve ser usada em seu favor.
  • Use UNITY_WSA para usar APIs do Unity específicas da UWP, como o namespace UnityEngine.XR.WSA. Isso será executado no Editor quando a plataforma for definida como UWP e em aplicativos UWP integrados.

Este gráfico pode ajudá-lo a decidir qual #if usar, dependendo dos casos de uso e das configurações de compilação esperadas.

Definir UWP IL2CPP UWP .NET Editor
UNITY_EDITOR Falso Falso True
UNITY_WSA True True True
WINDOWS_UWP True True False
UNITY_WSA && !UNITY_EDITOR True True False
ENABLE_WINMD_SUPPORT True True Falso
NETFX_CORE Falso True Falso

Prefira DateTime.UtcNow em vez de DateTime.Now

DateTime.UtcNow é mais rápido que DateTime.Now. Verificamos em investigações de desempenho anteriores que o uso de DateTime.Now adiciona sobrecarga significativa, especialmente quando usado no loop Update(). Outras pessoas encontraram o mesmo problema.

Prefira usar DateTime.UtcNow, a menos que você realmente precise dos horários localizados (um dos possíveis motivos pode ser quando você quiser mostrar a hora atual no fuso horário do usuário). Se você estiver lidando com horas relativas (ou seja, o delta entre alguma última atualização e agora), é melhor usar DateTime.UtcNow para evitar a sobrecarga de conversões de fuso horário.