Instrucciones de codificación

En este documento se describen los principios y convenciones de codificación que se deben seguir al contribuir a MRTK.


Filosofía

Sea conciso y esfuérzate por la simplicidad

La solución más sencilla suele ser la mejor. Este es un objetivo reemplazable de estas directrices y debe ser el objetivo de toda la actividad de codificación. Parte de ser simple es ser conciso y coherente con el código existente. Intente que el código sea sencillo.

Los lectores solo deben encontrar artefactos que proporcionen información útil. Por ejemplo, los comentarios que restablecen lo obvio no proporcionan información adicional y aumentan la proporción de ruido y señal.

Mantenga la lógica de código simple. Tenga en cuenta que no se trata de una instrucción sobre cómo usar el menor número de líneas, minimizar el tamaño de los nombres de identificador o el estilo de llaves, sino reducir el número de conceptos y maximizar la visibilidad de las líneas a través de patrones conocidos.

Generar código coherente y legible

La legibilidad del código se correlaciona con tasas de defectos bajas. Esfuérzse por crear código que sea fácil de leer. Esfuérzate por crear código que tenga una lógica simple y vuelva a usar los componentes existentes, ya que también ayudará a garantizar la corrección.

Todos los detalles del código que se generan importan, desde los detalles más básicos de la corrección hasta el estilo y el formato coherentes. Mantenga el estilo de codificación coherente con lo que ya existe, incluso si no coincide con sus preferencias. Esto aumenta la legibilidad del código base general.

Compatibilidad con la configuración de componentes en el editor y en tiempo de ejecución

MRTK admite un conjunto diverso de usuarios: personas que prefieren configurar componentes en el editor de Unity y cargar elementos prefijos, y personas que necesitan crear instancias y configurar objetos en tiempo de ejecución.

Todo el código debe funcionar agregando un componente a un GameObject en una escena guardada y mediante la creación de instancias de ese componente en el código. Las pruebas deben incluir un caso de prueba para crear instancias previas y crear instancias, y configurar el componente en tiempo de ejecución.

Play-in-editor es la primera plataforma de destino principal

Play-In-Editor es la manera más rápida de iterar en Unity. Proporcionar maneras a nuestros clientes de iterar rápidamente les permite desarrollar soluciones más rápidamente y probar más ideas. En otras palabras, maximizar la velocidad de iteración permite a nuestros clientes lograr más.

Haga que todo funcione en el editor y, a continuación, haga que funcione en cualquier otra plataforma. Siga funcionando en el editor. Es fácil agregar una nueva plataforma a Play-In-Editor. Es muy difícil hacer que Play-In-Editor funcione si la aplicación solo funciona en un dispositivo.

Agregar nuevos campos públicos, propiedades, métodos y campos privados serializados con cuidado

Cada vez que se agrega un método público, un campo o una propiedad, se convierte en parte de la superficie de API pública de MRTK. Los campos privados marcados con [SerializeField] también exponen campos al editor y forman parte de la superficie de API pública. Otras personas pueden usar ese método público, configurar elementos prefab personalizados con el campo público y tomar una dependencia de él.

Los nuevos miembros públicos deben examinarse detenidamente. Cualquier campo público deberá mantenerse en el futuro. Recuerde que si el tipo de un campo público (o campo privado serializado) cambia o se quita de un monobehaviour, esto podría interrumpir a otras personas. El campo deberá estar en desuso para una versión y es necesario proporcionar código para migrar los cambios de las personas que han tomado dependencias.

Priorizar la escritura de pruebas

MRTK es un proyecto de la comunidad, modificado por una amplia gama de colaboradores. Es posible que estos colaboradores no conozcan los detalles de la corrección o característica de errores y que la característica se rompa accidentalmente. MRTK ejecuta pruebas de integración continuas antes de completar todas las solicitudes de incorporación de extracción. Los cambios que interrumpirán las pruebas no se pueden comprobar. Por lo tanto, las pruebas son la mejor manera de asegurarse de que otras personas no interrumpirán la característica.

Cuando corrija un error, escriba una prueba para asegurarse de que no se revierte en el futuro. Si agrega una característica, escriba pruebas que comprueben que la característica funciona. Esto es necesario para todas las características de la experiencia de usuario, excepto las características experimentales.

Convenciones de codificación de C#

Encabezados de información de licencia de script

Todos los empleados de Microsoft que contribuyen a nuevos archivos deben agregar el siguiente encabezado de licencia estándar en la parte superior de los archivos nuevos, exactamente como se muestra a continuación:

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

Encabezados de resumen de función/método

Todas las clases públicas, structs, enumeraciones, funciones, propiedades, campos publicados en MRTK deben describirse como su propósito y uso, exactamente como se muestra a continuación:

/// <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;
}

Esto garantiza que la documentación se genera y se propaga correctamente para todas las clases, métodos y propiedades.

Se rechazarán los archivos de script enviados sin las etiquetas de resumen adecuadas.

Reglas de espacio de nombres de MRTK

El Mixed Reality Toolkit un modelo de espacio de nombres basado en características, donde todos los espacios de nombres fundamentales comienzan por "Microsoft.MixedReality. Toolkit". En general, no es necesario especificar la capa del kit de herramientas (por ejemplo, Core, Providers, Services) en los espacios de nombres.

Los espacios de nombres definidos actualmente son:

  • Microsoft.MixedReality. Toolkit
  • Microsoft.MixedReality. Toolkit. Límite
  • Microsoft.MixedReality. Toolkit. Diagnóstico
  • Microsoft.MixedReality. Toolkit. Editor
  • Microsoft.MixedReality. Toolkit. Entrada
  • Microsoft.MixedReality. Toolkit. SpatialAwareness
  • Microsoft.MixedReality. Toolkit. Teleport
  • Microsoft.MixedReality. Toolkit. Utilidades

En el caso de los espacios de nombres con una gran cantidad de tipos, es aceptable crear un número limitado de subparátipos de nombres para ayudar a limitar el uso del ámbito.

Si se omite el espacio de nombres de una interfaz, una clase o un tipo de datos, se bloqueará el cambio.

Adición de nuevos scripts de MonoBehaviour

Al agregar nuevos scripts MonoBehaviour con una solicitud de incorporación de cambios, asegúrese de que el atributo se aplica a AddComponentMenu todos los archivos aplicables. Esto garantiza que el componente se pueda detectar fácilmente en el editor en el botón Agregar componente. La marca de atributo no es necesaria si el componente no se puede mostrar en el editor, como una clase abstracta.

En el ejemplo siguiente, el paquete debe rellenarse con la ubicación del paquete del componente. Si coloca un elemento en la carpeta MRTK/SDK, el paquete será SDK.

[AddComponentMenu("Scripts/MRTK/{Package here}/MyNewComponent")]
public class MyNewComponent : MonoBehaviour

Adición de nuevos scripts de inspector de Unity

En general, intente evitar la creación de scripts de inspector personalizados para los componentes de MRTK. Agrega sobrecarga adicional y administración del código base que podría controlar el motor de Unity.

Si se necesita una clase inspectora, intente usar el elemento de DrawDefaultInspector() Unity. Esto simplifica de nuevo la clase inspector y deja gran parte del trabajo a Unity.

public override void OnInspectorGUI()
{
    // Do some custom calculations or checks
    // ....
    DrawDefaultInspector();
}

Si se requiere representación personalizada en la clase inspector, intente utilizar SerializedProperty y EditorGUILayout.PropertyField . Esto garantizará que Unity controla correctamente la representación de elementos prefabs anidados y valores modificados.

Si no se puede usar debido a un requisito en la lógica personalizada, asegúrese de que todo EditorGUILayout.PropertyField el uso se ajusta alrededor de EditorGUI.PropertyScope . Esto garantizará que Unity represente el inspector correctamente para los elementos prefab anidados y los valores modificados con la propiedad dada.

Además, intente decorar la clase inspectora personalizada con CanEditMultipleObjects un . Esta etiqueta garantiza que se pueden seleccionar y modificar juntos varios objetos con este componente en la escena. Las nuevas clases de inspector deben probar que su código funciona en esta situación en la escena.

    // Example inspector class demonstrating usage of SerializedProperty & EditorGUILayout.PropertyField
    // as well as use of EditorGUI.PropertyScope for custom property logic
    [CustomEditor(typeof(MyComponent))]
    public class MyComponentInspector : UnityEditor.Editor
    {
        private SerializedProperty myProperty;
        private SerializedProperty handedness;

        protected virtual void OnEnable()
        {
            myProperty = serializedObject.FindProperty("myProperty");
            handedness = serializedObject.FindProperty("handedness");
        }

        public override void OnInspectorGUI()
        {
            EditorGUILayout.PropertyField(destroyOnSourceLost);

            Rect position = EditorGUILayout.GetControlRect();
            var label = new GUIContent(handedness.displayName);
            using (new EditorGUI.PropertyScope(position, label, handedness))
            {
                var currentHandedness = (Handedness)handedness.enumValueIndex;

                handedness.enumValueIndex = (int)(Handedness)EditorGUI.EnumPopup(
                    position,
                    label,
                    currentHandedness,
                    (value) => {
                        // This function is executed by Unity to determine if a possible enum value
                        // is valid for selection in the editor view
                        // In this case, only Handedness.Left and Handedness.Right can be selected
                        return (Handedness)value == Handedness.Left
                        || (Handedness)value == Handedness.Right;
                    });
            }
        }
    }

Adición de nuevos objetos ScriptableObjects

Al agregar nuevos scripts ScriptableObject, asegúrese de CreateAssetMenu que el atributo se aplica a todos los archivos aplicables. Esto garantiza que el componente se pueda detectar fácilmente en el editor a través de los menús de creación de recursos. La marca de atributo no es necesaria si el componente no se puede mostrar en el editor, como una clase abstracta.

En el ejemplo siguiente, la subcarpeta debe rellenarse con la subcarpeta MRTK, si procede. Si coloca un elemento en la carpeta MRTK/Providers, el paquete será Providers. Si coloca un elemento en la carpeta MRTK/Core, establezca esta opción en "Perfiles".

En el ejemplo siguiente, el | MyNewProvider debe rellenarse con el nombre de la nueva clase, si procede. Si coloca un elemento en la carpeta MixedRealityToolkit, deje esta cadena fuera.

[CreateAssetMenu(fileName = "MyNewProfile", menuName = "Mixed Reality Toolkit/{Subfolder}/{MyNewService | MyNewProvider}/MyNewProfile")]
public class MyNewProfile : ScriptableObject

Registro

Al agregar nuevas características o actualizar las características existentes, considere la posibilidad de agregar registros DebugUtilities.LogVerbose a código interesante que puede ser útil para la depuración futura. Aquí hay un equilibrio entre agregar registro y el ruido agregado y no hay suficiente registro (lo que dificulta el diagnóstico).

Un ejemplo interesante en el que el registro es útil (junto con una carga útil interesante):

DebugUtilities.LogVerboseFormat("RaiseSourceDetected: Source ID: {0}, Source Type: {1}", source.SourceId, source.SourceType);

Este tipo de registro puede ayudar a detectar problemas como , causados por eventos de origen no https://github.com/microsoft/MixedRealityToolkit-Unity/issues/8016 coincidentes detectados y pérdidas de origen.

Evite agregar registros para los datos y eventos que se producen en cada fotograma; idealmente, el registro debe cubrir eventos "interesantes" controlados por distintas entradas de usuario (es decir, un "clic" por parte de un usuario y el conjunto de cambios y eventos que proceden de que son interesantes de registrar). El estado continuo de "el usuario sigue manteniendo un gesto" registrado en cada fotograma no es interesante y sobrecargará los registros.

Tenga en cuenta que este registro detallado no está activado de forma predeterminada (debe estar habilitado en la configuración del sistema de diagnóstico).

Espacios frente a pestañas

Asegúrese de usar 4 espacios en lugar de pestañas al contribuir a este proyecto.

Espaciado

No agregue espacios adicionales entre corchetes y paréntesis:

Lo que debe evitar:

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

Lo que es necesario hacer:

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

Convenciones de nomenclatura

Use siempre PascalCase para las propiedades. Se camelCase usa para la mayoría de los campos, excepto para los campos y PascalCase static readonly const . La única excepción a esto es para las estructuras de datos que requieren que los campos sean serializados por JsonUtility .

Lo que debe evitar:

public string myProperty; // <- Starts with a lowercase letter
private string MyField; // <- Starts with an uppercase letter

Lo que es necesario hacer:

public string MyProperty;
protected string MyProperty;
private static readonly string MyField;
private string myField;

Modificadores de acceso

Declare siempre un modificador de acceso para todos los campos, propiedades y métodos.

  • Todos los métodos de LA API de Unity deben ser de forma predeterminada, a menos que private tenga que invalidarlos en una clase derivada. En este protected caso, se debe usar .

  • Los campos siempre deben ser private , con public los protected accessors de propiedad o .

  • Usar miembros con forma de expresión y propiedades automáticas siempre que sea posible

Lo que debe evitar:

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

// property should have protected setter
public int MyVariable => myVariable;

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

Lo que es necesario hacer:

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

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

Usar llaves

Use siempre llaves después de cada bloque de instrucciones y colóctelas en la línea siguiente.

Cosas que evitar

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

Lo que debe evitar:

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

Lo que es necesario hacer:

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

Las clases públicas, las estructuras y las enumeraciones deben ir en sus propios archivos.

Si la clase, la estructura o la enumeración se pueden convertir en privadas, es correcto que se incluyan en el mismo archivo. Esto evita problemas de compilación con Unity y garantiza que se produce la abstracción de código adecuada, también reduce los conflictos y los cambios importantes cuando el código necesita cambiar.

Lo que debe evitar:

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

Lo que es necesario hacer:

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

Cosas que hacer

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;
}

Inicialización de enumeraciones

Para asegurarse de que todas las enumeraciones se inicializan correctamente a partir de 0, .NET proporciona un acceso directo ordenado para inicializar automáticamente la enumeración agregando simplemente el primer valor (inicio). (Por ejemplo, valor 1 = 0 No se requieren valores restantes)

Lo que debe evitar:

public enum Value
{
    Value1, <- no initializer
    Value2,
    Value3
}

Lo que es necesario hacer:

public enum ValueType
{
    Value1 = 0,
    Value2,
    Value3
}

Ordenación de enumeraciones para la extensión adecuada

Es fundamental que, si es probable que una enumeración se pueda extender en el futuro, para ordenar los valores predeterminados en la parte superior de la enumeración, esto garantiza que los índices de enumeración no se ven afectados con nuevas adiciones.

Lo que debe evitar:

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

Lo que es necesario hacer:

/// <summary>
/// The SDKType lists the VR SDKs that are supported by the MRTK
/// Initially, this lists proposed SDKs, 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
}

Revisión del uso de enumeración para campos de bits

Si existe la posibilidad de que una enumeración requiera varios estados como valor, por ejemplo, Handedness = Left & Right. A continuación, la enumeración debe decorarse correctamente con BitFlags para que se pueda usar correctamente.

El archivo Handedness.cs tiene una implementación concreta para esto.

Lo que debe evitar:

public enum Handedness
{
    None,
    Left,
    Right
}

Lo que es necesario hacer:

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

Rutas de acceso de archivo codificadas de forma fuerte

Al generar rutas de acceso de archivo de cadena y, en particular, escribir rutas de acceso de cadena codificadas de forma fuerte, haga lo siguiente:

  1. Use las API de C# Path siempre que sea posible, como Path.Combine o Path.GetFullPath .
  2. Use / o Path.DirectorySeparatorChar en lugar de \ o \ \ .

Estos pasos garantizan que MRTK funciona en sistemas basados Windows y Unix.

Lo que debe evitar:

private const string FilePath = "MyPath\\to\\a\\file.txt";
private const string OtherFilePath = "MyPath\to\a\file.txt";

string filePath = myVarRootPath + myRelativePath;

Lo que es necesario hacer:

private const string FilePath = "MyPath/to/a/file.txt";
private const string OtherFilePath = "folder{Path.DirectorySeparatorChar}file.txt";

string filePath = Path.Combine(myVarRootPath,myRelativePath);

// Path.GetFullPath() will return the full length path of provided with correct system directory separators
string cleanedFilePath = Path.GetFullPath(unknownSourceFilePath);

Procedimientos recomendados, incluidas las recomendaciones de Unity

Algunas de las plataformas de destino de este proyecto requieren tener en cuenta el rendimiento. Con esto en mente, tenga siempre cuidado al asignar memoria en código con frecuencia llamado en bucles de actualización o algoritmos estrictos.

Encapsulación

Use siempre campos privados y propiedades públicas si se necesita acceso al campo desde fuera de la clase o struct. Asegúrese de colocar el campo privado y la propiedad pública. Esto facilita ver, de un vistazo, lo que hace detrás de la propiedad y que el campo se puede modifica mediante script.

Nota

La única excepción a esto es para las estructuras de datos que requieren que los campos sean serializados por , donde se requiere que una clase de datos tenga todos los campos públicos para que funcione JsonUtility la serialización.

Lo que debe evitar:

private float myValue1;
private float myValue2;

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

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

Lo que es necesario hacer:

// 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 => myValue;
    set => myValue = value;
}

/// <summary>
/// Getter/Setters not wrapping a value directly should contain documentation comments just as public functions would
/// </summary>
public float AbsMyValue
{
    get
    {
        if (MyValue < 0)
        {
            return -MyValue;
        }

        return MyValue
    }
}

Almacenar en caché valores y serializarlos en la escena o prefab siempre que sea posible

Con la HoloLens en mente, es mejor optimizar el rendimiento y almacenar en caché las referencias en la escena o el objeto prefab para limitar las asignaciones de memoria en tiempo de ejecución.

Lo que debe evitar:

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

Lo que es necesario hacer:

[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);
}

Almacenar en caché referencias a materiales, no llamar a ".material" cada vez

Unity creará un nuevo material cada vez que use ".material", lo que provocará una pérdida de memoria si no se limpia correctamente.

Lo que debe evitar:

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

Lo que es necesario hacer:

// 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);
    }
}

Nota

Como alternativa, use la propiedad "SharedMaterial" de Unity, que no crea un nuevo material cada vez que se hace referencia a él.

Use la compilación dependiente de la plataforma para Toolkit no interrumpirá la compilación en otra plataforma.

  • Use WINDOWS_UWP para usar API específicas de UWP que no son de Unity. Esto impedirá que intenten ejecutarse en el editor o en plataformas no admitidas. Esto es equivalente a UNITY_WSA && !UNITY_EDITOR y debe usarse en favor de .
  • Use para usar LAS API de UNITY_WSA Unity específicas de UWP, como el espacio de UnityEngine.XR.WSA nombres . Esto se ejecutará en el Editor cuando la plataforma esté establecida en UWP, así como en aplicaciones para UWP integradas.

Este gráfico puede ayudarle a decidir qué usar, en función de los casos #if de uso y la configuración de compilación que espera.

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

Preferir DateTime.UtcNow en lugar de DateTime.Now

DateTime.UtcNow es más rápido que DateTime.Now. En investigaciones de rendimiento anteriores, hemos descubierto que el uso de DateTime.Now agrega una sobrecarga significativa especialmente cuando se usa en el bucle Update(). Otros han tenido el mismo problema.

Prefiere usar DateTime.UtcNow a menos que realmente necesite las horas localizadas (un motivo legítimo puede ser que quiera mostrar la hora actual en la zona horaria del usuario). Si está trabajando con horas relativas (es decir, la diferencia entre alguna última actualización y ahora), es mejor usar DateTime.UtcNow para evitar la sobrecarga de realizar conversiones de zona horaria.

Convenciones de codificación de PowerShell

Un subconjunto del código base de MRTK usa PowerShell para la infraestructura de canalización y varios scripts y utilidades. El nuevo código de PowerShell debe seguir el estilo PoshCode.

Consulte también

Convenciones de codificación de C# de MSDN