Saltar a contenido

Prácticas de desarrollo

Manual del programador

Lo que sigue a continuación es una descripción lo más exhaustiva posible sobre el estilo en que se debe programar en GLA. Los ejemplos están todos en C# pero en la mayoría de los casos esto debería ser anecdótico. Hay que notar que hay algo de arbitrario en toda confección de un manual de estilos; el objetivo es más que nada establecer una práctica y un criterio común para poder lograr integridad y facilitar la lectura y la adopción.

Convenciones de nombrado y otras prácticas

Respetamos y seguimos las siguientes convenciones y prácticas:

  • PascalCase al nombrar clases, métodos y propiedades.
public class GestorDeEventos { ... }
public void Execute(object param) { ... }
public string ConnectionInfo { get; set; }
  • camelCase al nombrar variables locales a un método.
MessageHandlerAdaptor handler;
  • camelCase con prefijo underscrore (_) al nombrar variables privadas de clase. Usar readonly cada vez que sea posible.
 private static readonly MqConfig _mqCfg;
 private readonly EndPoints _endPointsCfg;
  • Las interfaces deben empezar con I. Ej: public interface IServicios { ... }

  • No usar nombres de variables de una sola letra. Una excepción son lo índices en los loops:

 for ( int i = 0; i < count; i++ ) { ... }
  • Los namespaces deben tener la siguiente forma: namespace Andreani.{NombreDelProyecto}.{ModuloSuperior}.{ModuloInferior} { .... }

  • El uso de las llaves es según el estilo Allman, cada llave { } va en una línea aparte.

 for (int j = 1; j \<= n; j++)
 {
  /// si son iguales en posiciones equidistantes el peso es 0
  /// de lo contrario el peso suma a uno.
  costo = (s[i - 1] == t[j - 1]) ? 0 : 1;
  d[i, j] = System.Math.Min(System.Math.Min(d[i - 1, j] + 1,  //Eliminacion
  di, j - 1] + 1), //Insercion
  d[i - 1, j - 1] + costo); //Sustitucion
 }

Salvo que sea una sola línea y entonces puede ir sin llaves:

if (!normalizador.VerificarSiPudoNormalizar(localidadNormalizada))
 return HttpStatusCode.NoContent;
  • Siempre especificar la visibilidad, aunque sea el default.
private MaquinaDeEstadosDeEnvio _maquinaDeEstados;
  • Evitar tener dos líneas en blanco seguidas.
  • Solo usar var cuando es obvio el tipo de dato a que se refiere:
var count = 1; //OK
  • En las clases definir las variables miembro arriba de todo.
  • Preferir string a String e int o long a Int32 o a Int64.
  • Evitar el uso de la palabra clave this.
  • El nombre de los métodos deben expresar una acción y los de las propiedades un atributo:
public void EntregarPorMostrador(Envio unEnvioEntregable) { ... } //OK
public void EstadoDeEnvio Estado { get; } //OK
public void RequisitosDeEntrega(Envio unEnvioEntregable) { ... } //ERROR
  • Preferimos que las propiedades y los métodos que devuelven bool se escriban como contestando una pregunta de respuesta verdadero o falso:
public bool EstaActivo { get; } //OK
public bool Activo { get; } // ERROR
  • Intentar que los argumentos de los métodos expresen bien qué clase de rol cumplen en la llamada:
public void ActualizarDestino(Geo unDestinoNuevo) { .. } //OK
public void ActualizarDestino(Geo geo) { ... } //ERROR
public void Agregar(Envio contenido) { ... } //OK
public void Agregar(Envio envio) { ... } //ERROR
  • Verificar si es más expresivo que los métodos que devuelvan colecciones empiecen con la palabra Listar. Y si son propiedades, deberían estar en plural.
public IList<Sucursal> ListarSucursalesDeUnaRegion(string elNombreDeUnaRegion) {...}
public ISet<double> Tarifas { get; set; } = new HashSet<double>();
  • No subir a GitHub bloques de código comentado de esta forma:
//public override int GetHashCode()
//{
//  return GeneradorDeHashCode.GetHashCodeFNV(Tipo, Modulo);
//}
//
//public override bool Equals(object obj)
//{
//  var otro = obj as IdClass;
//  return otro != null && otro.Tipo == Tipo && otro.Modulo == Modulo;
//}

Si vas a comentar toda una parte, borrála directamente o especificá claramente por qué. - Las excepciones personalizadas deben nombrarse terminando con la palabra Exception:

public class NoEsUnContenedorException : ArgumentException { ... }

Comentarios

Los metodos que precisen comentarios se comentan utilizando el estándar de VisualStudio:

// <summary>
/// Actualiza el estado de un contenedorDistribuible a partir de un evento
/// </summary>
/// <param name="evento"><param>
/// <returns>true si se produjo un cambio de estado\<returns>
protected virtual bool ActualizarEstado(EventosDeContenedoresNoDistribuibles evento)

Evitar comentarios excesivos. Esforzarse por escribir el código con una claridad tal de que estos no sean necesarios. Si creés que del código no se puede inferir lo que hace con una lectura rápida, entonces ahí es recomendable una aclaración.

No alcanza con que la computadora entienda el programa, otra persona también tiene que entenderlo.

En una jerarquía de clases, siempre comentar el método más abstracto. Recordar que nuestro diseño también debe respetar el principio de sustitución de Liskov. Las sucesivas sobrecargas en las subclases deben utilizar \

///<summary>
/// Si el contenedor esta abierto
///</summary>
bool EstaAbierto { get; }

/// <inheritdoc />
 public virtual bool EstaAbierto => InformacionDeEstado.Estado == CASADefinesV2.EntityNumberState.Open;

Excepciones

Nosotros hacemos un uso extensivo de las excepciones para gestionar el flujo de ejecución en presencia de errores. Por lo tanto, seguir las siguientes recomendaciones: - Arrojar excepciones cuando el método se encuentra con una situación anormal y no puede seguir.

Nota Se sabe que es difícil de discernir si una situación es anormal y que hay veces que lo más indicado es devolver un valor inválido o un código de error. Ante la duda, arrojá un excepción. Supongamos este ejemplo:

           Stream str = ...
           if (stream.Read()>0) 
           { .... }    
Bien se podría haber arrojado una excepción en el Read() si es que se llegó al final del stream, pero como Read() devuelve la cantidad de bytes leídos y que el stream finalice no debería ser una situación anormal (todos los archivos terminan), el diseñador prefirió devolver -1 en este caso.

  • Nunca arrojar System.Exception.
  • Intentar en lo posible atrapar la excepción mas concreta. Por ejemplo, ArgumentNullException a ArgumentException. Verificar el código que se llama para ver qué excepciones puede arrojar y gestionarlas.
  • Nunca silenciar una excepción. Hay casos especiales en que puede ser válido, pero en la mayoría de los casos es una mala práctica:
try 
{
    //...
}
catch (Exception e)
{ 
}
  • En presencia de excepciones, el objeto que la arroja debe quedar en un estado estable, es decir, debe mantener sus invariantes. No es válido que un objeto quede con valores ilegales luego de arrojar una excepción. Por lo tanto, cuando la arrojes, asegurate que el objeto que se intentó modificar quede con los valores que tenía antes de que alguien invoque al método que causó la excepción.
  • Si existen excepciones del framework que expresan la condición, usar esas y no personalizadas.
  • Si vas a usar una excepción personalizada la misma debería tener funcionalidad que permita saber mejor qué fue lo que pasó, o que describa información de contexto.
public class ObjetoNoEncontradoException : ArgumentException
   {
       public ObjetoNoEncontradoException(object id, Type @class, string msg = null, Exception inner = null) : base(msg, inner)
       {
           Id = id;
           Clase = @class;
       }
       public object Id { get; private set; }
       public Type Clase { get; private set; }
   }

Además, siempre poner el argumento para pasarle a la superclase el inner exception y el mensaje. - Usar nameof(paramName) y no "paramName" al arrojar una excepción que tome un nombre de parámetro como argumento.

throw new ArgumentNullException(nameof(numeroDeEnvio)); //OK
throw new ArgumentNullException("numeroDeEnvio"); //ERROR
  • Es importante también documentar qué excepciones arroja un método:
/// <summary>
/// <para>str</para> debe venir en la forma en que lo devuelve Rango.ToString()
/// </summary>
/// <param name="str">El rango a parsear, ej: 1 - 10</param>
/// <returns></returns>
/// <exception cref="NotSupportedException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="NullReferenceException"></exception>
public static Rango<T> Parse(string str) { ... }
  • Si hay que relanzar la excepción en un catch, preferir la cláusula throw solitaria. Esto preserva el callstack. Ejemplo:
try 
{
    //....
} 
catch(Exception e)
{
    // ....
    throw e; //ERROR
    throw; //OK
}

Cross-Platform

El software que hacemos debe correr también en Linux. Por lo tanto hay que tener en cuenta lo siguiente:

  • Usar Enviroment.NewLine en lugar de harcodear los finales de línea. Windows usa \r\n y OSX/Linux usan \n.

  • Usar Path.Combine() o Path.DirectorySeparatorChar para separar directorios. Si no es posible, usar / (barra hacia adelante) como en C:/IntegraCoreServices/MiApp/Logs

Consideraciones de diseño.

Pensar bien los nombres de las clases, propiedades y métodos. Encontrar el nombre que mejor represente el concepto que se quiere modelar. Preguntarle a algún compañero/a opiniones al respecto. Recordar que si no sabés que nombre ponerle a una clase es porque seguramente no sabés qué es lo que querés representar realmente. Lo mismo para los métodos y propiedades.

El modelo de dominio siempre hay que programarlo en el idioma del negocio, esto es, castellano. Los usuarios no dicen shipment sino envio; y hay cuestiones culturales que son muy difíciles de traducir. Vos estás leyendo esto porque sos un programador y no un sociólogo. Nosotros podemos ayudarle al negocio a pensar pero no inventamos el léxico, lo encontramos con ellos si hace falta. Además, nadie domina realmente el inglés (o son pocos los casos) como para tener la destreza de encontrar la palabra adecuada en todos los casos: ya nos cuesta demasiado en español. Pueden ver estrategias del dominio en DDD. Aclaración no es necesario que el codigo este escrito integramente en español ya que mucha veces es imposible por el uso de librerias externas, lo que se quiere pedir es que las acciones del negocio pueden estar en español para poder tener reflejado el negocio en el código.

La forma correcta de programar es adoptar una posición defensiva. Nunca confíes en que el que llama a tu código va a pasar los argumentos de forma correcta ni que el código que vas a llamar va a resultar sin excepciones. Proteger siempre el código chequeando el valor correcto de los parámetros y si no validan arrojar la excepción adecuada. Si vas a llamar a un método que no es tuyo, intentar averiguar qué excepciones puede arrojar y estar preparado para gestionarlas. Librerías como Polly pueden ayudar mucho cuando tu código tiene que llamar a otro menos confiable.

No diseñes para adelante. Nunca se sabe lo que el PO puede pedir. Es preferible siempre solamente hacer las tareas necesarias para cumplir con el objetivo del Sprint y nada más. Diseñar por las dudas es un anti-patrón, lo que hacemos es refactorizar en base a los requisitos nuevos.

Seguir las siguientes prácticas generales:

  • Mantener el cuerpo de los métodos corto. Evitar clases con muchos métodos.
  • Usar string.IsNullOrEmpty() y no miString == "".
  • La formar correcta de comparar strings es la siguiente:
     if (string.Compare(str1,str2,StringComparison.InvariantCultureIgnoreCase) == 0) 
        // son iguales
     else
        // son distintos
    
  • Al concatenar strings usar string interpolation o StringBuilder.
 var mensaje = "Hola " + usuario; //ERROR
 var mensaje = $"Hola {usuario}"; //OK
  • La visibildad es importante. Si el método/propiedad puede ser privado que sea privado.
  • Hacer estáticos los métodos que no accedan a ninguna propiedad de instancia.
  • Siempre que se sobrecarga GetHashCode() hay que sobrecargar Equals().
  • Inicializar las auto-properties y las variables miembro con valores default cuando sea posible y no en el constructor.
public class Foo
{
    public string Bar { get; set; } = "bar";
}
  • En lo posible, exponer las interfaces de colecciones más estríctas. Preferir IEnumerable<string> a ICollection<string> y esta última a List<string>. Pensá que el llamador puede modificar la colección sin que tu clase lo sepa. Eso a veces puede no ser lo mejor.

Diseño de APIs

Cuando un servicio deba exportar APIs debe seguir el diseño especificado por el estandar REST aquí si es una api interna debe contener la siguiente nomenclatura:

https://{nombre-de-app}-{proyecto}.apps.{ambiente}.andreani.com.ar

Ejemplo:

https://go-bridge-architecture-it-test.apps.ocptest.andreani.com.ar

Es conveniente que las apis cuenten con documentacion para que quienes la quieran usar puedan hacerlo fácil y rápida. La documentación deberá hacerse en formato OpenApi y debe ser accesible desde http://mihost.com/api/api-docs.

Integraciones

Introducción a la intregración por mensajería.

Toda comunicación entre sistemas es preferible hacerla de forma asincrónica.

Llamar a otras APIs. Cuestiones de dependencia.

Patrones de diseño para operaciones diferidas.

Patrones sharding.