paint-brush
Los 25 mejores consejos de programación en C#por@wownetort
16,077 lecturas
16,077 lecturas

Los 25 mejores consejos de programación en C#

por Nikita Starichenko8m2020/12/20
Read on Terminal Reader
Read this story w/o Javascript

Demasiado Largo; Para Leer

¡Hola a todos! Hay mucha información sobre las diferentes funciones de C#. Sobre varios trucos y mejores prácticas en este idioma. Quiero contarte algunos consejos igualmente útiles, pero menos populares, para trabajar con este lenguaje.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - Los 25 mejores consejos de programación en C#
Nikita Starichenko HackerNoon profile picture

¡Hola a todos! Hay mucha información sobre las diferentes funciones de C#. Sobre varios trucos y mejores prácticas en este idioma.
Quiero contarte algunos consejos igualmente útiles, pero menos populares, para trabajar con este lenguaje.

1. Los métodos "Task/Task<T>" no asíncronos no deberían devolver un valor nulo

Devolver nulo desde un método Task/Task<T> no asíncrono provocará una NullReferenceException en tiempo de ejecución. Este problema se puede evitar devolviendo Task.FromResult<T>(null) en su lugar.

Mal ejemplo:

 public Task< object > GetFooAsync ( ) { return null ; // Noncompliant }

Buen ejemplo:

 public Task< object > GetFooAsync ( ) { return Task.FromResult< object >( null ); }


2. Las cadenas no deben concatenarse usando '+' en un bucle

StringBuilder es más eficiente que la concatenación de cadenas, especialmente cuando el operador se repite una y otra vez como en bucles.

Mal ejemplo:

 string str = "" ; for ( int i = 0 ; i < arrayOfStrings.Length ; ++i) { str = str + arrayOfStrings[i]; }

Buen ejemplo:

 StringBuilder bld = new StringBuilder(); for ( int i = 0 ; i < arrayOfStrings.Length; ++i) { bld.Append(arrayOfStrings[i]); } string str = bld.ToString();


3. Se deben preferir los métodos basados en compensaciones de cadenas para encontrar subcadenas a partir de compensaciones.

La búsqueda de una subcadena determinada a partir de un desplazamiento específico se puede lograr mediante dicho código: str.Substring(startIndex).IndexOf(char1). Esto funciona bien, pero crea una nueva cadena para cada llamada al método Substring. Cuando esto se hace en un bucle, se crean muchas cadenas de forma gratuita, lo que puede provocar problemas de rendimiento si str es grande.

Para evitar problemas de rendimiento, string.Substring(startIndex) no debe encadenarse con los siguientes métodos:

  • Índice de
  • IndexOfAny
  • ÚltimoÍndiceDe
  • LastIndexOfAny

Para cada uno de estos métodos, hay disponible otro método con un parámetro adicional para especificar un desplazamiento.

El uso de estos métodos da el mismo resultado y evita la creación de instancias de String adicionales.

Mal ejemplo:

 str.Substring(StartIndex).IndexOf(char1); // Noncompliant; a new string is going to be created by "Substring"

Buen ejemplo:

 str.IndexOf(char1, startIndex);


4. Las colecciones no deben pasarse como argumentos a sus propios métodos.

Pasar una colección como argumento al propio método de la colección es un error (se pretendía algún otro argumento) o simplemente un código sin sentido.

Además, dado que algunos métodos requieren que el argumento no se modifique durante la ejecución, pasar una colección a sí misma puede provocar un comportamiento inesperado.

Malos ejemplos:

 var list = new List< int >(); list.AddRange(list); // Noncompliant list.Concat(list); // Noncompliant list.Union(list); // Noncompliant; always returns list list.Except(list); // Noncompliant; always empty list.Intersect(list); // Noncompliant; always list list.SequenceEqual(list); // Noncompliant; always true var set = new HashSet< int >(); set .UnionWith( set ); // Noncompliant; no changes set .ExceptWith( set ); // Noncompliant; always empty set .IntersectWith( set ); // Noncompliant; no changes set .IsProperSubsetOf( set ); // Noncompliant; always false set .IsProperSupersetOf( set ); // Noncompliant; always false set .IsSubsetOf( set ); // Noncompliant; always true set .IsSupersetOf( set ); // Noncompliant; always true set .Overlaps( set ); // Noncompliant; always true set .SetEquals( set ); // Noncompliant; always true set .SymmetricExceptWith( set ); // Noncompliant; always empty


5. Las matrices y colecciones vacías deben devolverse en lugar de nulo

Devolver nulo en lugar de una matriz o colección real obliga a las personas que llaman al método a probar explícitamente la nulidad, haciéndolos más complejos y menos legibles.

Además, en muchos casos, null se usa como sinónimo de vacío.

Malos ejemplos:

 public Result[] GetResults ( ) { return null ; // Noncompliant } public IEnumerable<Result> GetResults ( ) { return null ; // Noncompliant }

Buenos ejemplos:

 public Result[] GetResults ( ) { return new Result[ 0 ]; } public IEnumerable<Result> GetResults ( ) { return Enumerable.Empty<Result>(); }


6. Los resultados de la división de enteros no deben asignarse a variables de punto flotante

Cuando la división se realiza en enteros, el resultado siempre será un entero. Puede asignar ese resultado a un doble, flotante o decimal con conversión de tipo automática, pero al comenzar como un int, es probable que el resultado no sea el esperado. Si el resultado de la división int se asigna a una variable de coma flotante, se habrá perdido la precisión antes de la asignación. En su lugar, al menos un operando debe convertirse o promoverse al tipo final antes de que se lleve a cabo la operación.

Ejemplos:

 decimal dec = 3 / 2 ; // Noncompliant decimal dec = ( decimal ) 3 / 2 ;

7. Los recursos compartidos no deben usarse para bloquear

Los recursos compartidos no deben usarse para el bloqueo, ya que aumenta la posibilidad de interbloqueos. Cualquier otro subproceso podría adquirir (o intentar adquirir) el mismo bloqueo para otro propósito no relacionado.

En su lugar, se debe usar una instancia de objeto dedicada para cada recurso compartido, para evitar interbloqueos o bloqueos de contención.

Los siguientes objetos se consideran recursos compartidos:

  • este
  • un objeto de tipo
  • un literal de cadena
  • una instancia de cadena


8. Los subprocesos no deben bloquear objetos con identidad débil

Un subproceso que adquiere un bloqueo en un objeto al que se puede acceder a través de los límites del dominio de la aplicación corre el riesgo de ser bloqueado por otro subproceso en un dominio de aplicación diferente. Se dice que los objetos a los que se puede acceder a través de los límites del dominio de la aplicación tienen una identidad débil. Los tipos con identidad débil son:

  • MarshalByRefObjeto
  • ExecutionEngineException
  • OutOfMemoryException
  • StackOverflowException
  • Cuerda
  • Información de miembro
  • Información de parámetros
  • Hilo


9. No se debe usar "Thread.Resume" ni "Thread.Suspend"

Thread.Suspend y Thread.Resume pueden dar resultados impredecibles, y ambos métodos han quedado obsoletos. De hecho, si Thread.Suspend no se usa con mucho cuidado, un subproceso puede suspenderse mientras se mantiene un candado, lo que lleva a un interbloqueo. Se deben utilizar otros mecanismos de sincronización más seguros, como Monitor, Mutex y Semaphore.


10. Las excepciones no deben volver a lanzarse explícitamente

Al volver a lanzar una excepción, debe hacerlo simplemente llamando a throw; y no throw exc;, porque el seguimiento de la pila se restablece con la segunda sintaxis, lo que dificulta mucho la depuración.

Ejemplos:

 try {} catch (ExceptionType1 exc) { Console.WriteLine(exc); throw exc; // Noncompliant; stacktrace is reset } catch (ExceptionType2 exc) { Console.WriteLine(exc); throw ; // Compliant } catch (ExceptionType3 exc) { throw new Exception( "My custom message" , exc); // Compliant; stack trace preserved }


11. No se deben lanzar excepciones de métodos inesperados

Se espera que algunos métodos se llamen con precaución, pero se espera que otros, como ToString, "simplemente funcionen". Lanzar una excepción de dicho método es probable que rompa el código de las personas que llaman de forma inesperada.

El problema ocurre cuando se lanza una excepción de cualquiera de los siguientes:

  • Accesores de eventos
  • Objeto.Equals
  • IEquatable.Equals
  • Obtener código hash
  • Encadenar
  • constructores estáticos
  • IDisposable.Dispose
  • operador ==, !=, <, >, <=, >=
  • operadores de conversión implícitos

Mal ejemplo:

 public override string ToString ( ) { if ( string .IsNullOrEmpty(Name)) { throw new ArgumentException( "..." ); // Noncompliant } }


12. Las excepciones generales nunca deben lanzarse

Lanzar excepciones generales como Exception, SystemException, ApplicationException, IndexOutOfRangeException, NullReferenceException, OutOfMemoryException y ExecutionEngineException evita que los métodos de llamada manejen excepciones verdaderas generadas por el sistema de manera diferente a los errores generados por la aplicación.


13. No se deben lanzar excepciones en bloques finalmente.

Lanzar una excepción desde dentro de un bloque finalmente enmascarará cualquier excepción que se haya lanzado previamente en el bloque try o catch, y el mensaje de excepción enmascarado y el seguimiento de la pila se perderán.


14. Los tipos de excepción deben ser "públicos"

El objetivo de tener tipos de excepción personalizados es transmitir más información de la que está disponible en los tipos estándar. Pero los tipos de excepción personalizados deben ser públicos para que funcionen.

Si un método arroja una excepción no pública, lo mejor que puede hacer en el lado de la persona que llama es capturar la base pública más cercana de la clase. Es decir, pierde toda la información personalizada que creó para pasar el tipo de excepción.


15. Los destructores no deben lanzar excepciones

Si Finalize o una anulación de Finalize genera una excepción, y el tiempo de ejecución no está alojado en una aplicación que anula la política predeterminada, el tiempo de ejecución finaliza el proceso inmediatamente sin una limpieza correcta (finalmente, los bloques y los finalizadores no se ejecutan). Este comportamiento garantiza la integridad del proceso si el finalizador no puede liberar o destruir recursos.

Mal ejemplo:

 class MyClass { ~MyClass() { throw new NotImplementedException(); // Noncompliant } }


16. Los "IDisposables" creados en una instrucción "using" no deben devolverse

Por lo general, querrá usar using para crear una variable IDisposable local; activará la eliminación del objeto cuando el control pase fuera del alcance del bloque. La excepción a esta regla es cuando su método devuelve ese IDisposable. En ese caso, el uso de desecha el objeto antes de que la persona que llama pueda usarlo, lo que probablemente provoque excepciones en el tiempo de ejecución. Por lo tanto, debe eliminar el uso o evitar devolver el IDisposable.

Mal ejemplo:

 public FileStream WriteToFile ( string path, string text ) { using ( var fs = File.Create(path)) // Noncompliant { var bytes = Encoding.UTF8.GetBytes(text); fs.Write(bytes, 0 , bytes.Length); return fs; } }


17. "operator==" no debe sobrecargarse en los tipos de referencia

Se espera que el uso de == para comparar objetos haga una comparación de referencia. Es decir, se espera que devuelva verdadero si y solo si son la misma instancia de objeto. Sobrecargar al operador para que haga cualquier otra cosa conducirá inevitablemente a la introducción de errores por parte de las personas que llaman. Por otro lado, sobrecargarlo para hacer exactamente eso no tiene sentido; eso es lo que == hace por defecto.

18. "Equals(Object)" y "GetHashCode()" deben anularse en pares

Existe un contrato entre Equals(objeto) y GetHashCode(): si dos objetos son iguales según el método Equals(objeto), llamar a GetHashCode() en cada uno de ellos debe arrojar el mismo resultado. Si este no es el caso, muchas colecciones no manejarán correctamente las instancias de clase.

Para cumplir con el contrato, Equals(object) y GetHashCode() deben heredarse o anularse.

19. "GetHashCode" no debe hacer referencia a campos mutables

GetHashCode se utiliza para archivar un objeto en un diccionario o tabla Hash. Si GetHashCode usa campos que no son de solo lectura y esos campos cambian después de almacenar el objeto, el objeto se archiva de forma incorrecta en Hashtable de inmediato. Cualquier prueba posterior para ver si el objeto está en Hashtable arrojará un falso negativo.

Mal ejemplo:

 public int age; public string name; public override int GetHashCode ( ) { int hash = 12 ; hash += this .age.GetHashCode(); // Noncompliant hash += this .name.GetHashCode(); // Noncompliant return hash; }

Buen ejemplo:

 public readonly DateTime birthday; public string name; public override int GetHashCode ( ) { int hash = 12 ; hash += this .birthday.GetHashCode(); return hash; }


20. Las clases "abstractas" no deberían tener constructores "públicos"

Dado que las clases abstractas no se pueden instanciar, no tiene sentido que tengan constructores públicos o internos. Si hay una lógica de inicialización básica que debe ejecutarse cuando se crea una instancia de clase extendida, puede ponerla en un constructor, pero hacer que ese constructor sea privado o protegido.


21. La herencia de tipos no debe ser recursiva

La recursión es aceptable en los métodos, en los que puede salir de ella. Pero con los tipos de clase, terminas con un código que se compilará pero no se ejecutará si intentas instanciar la clase.
Mal ejemplo:

 class C1 < T > { } class C2 < T > : C1 < C2 < C2 < T >>> // Noncompliant { } var c2 = new C2< int >();

22. No se debe usar "nuevo Guid ()"

Cuando se usa la sintaxis new Guid() (es decir, creación de instancias sin parámetros), debe ser que se desea una de estas tres cosas:

  1. Un GUID vacío, en cuyo caso Guid.Empty es más claro.
  2. Un GUID generado aleatoriamente, en cuyo caso se debe usar Guid.NewGuid().
  3. Un nuevo GUID con una inicialización específica, en cuyo caso falta el parámetro de inicialización.



23. "GC.Collect" no debe llamarse

Llamar a GC.Collect rara vez es necesario y puede afectar significativamente el rendimiento de la aplicación. Esto se debe a que activa una operación de bloqueo que examina todos los objetos en la memoria para limpiarlos. Además, no tiene control sobre cuándo se ejecutará realmente esta limpieza de bloqueo.

Como regla general, las consecuencias de llamar a este método superan con creces los beneficios, a menos que tal vez haya desencadenado algún evento único en la ejecución de su programa que causó la muerte de muchos objetos de larga duración.


24. Las secciones de código no deben comentarse

Los programadores no deben comentar el código, ya que infla los programas y reduce la legibilidad.

El código no utilizado debe eliminarse y puede recuperarse del historial de control de código fuente si es necesario.


25. No se debe usar la declaración "goto"

goto es una instrucción de flujo de control no estructurada. Hace que el código sea menos legible y mantenible. En su lugar, se deben utilizar declaraciones de flujo de control estructurado como if, for, while, continue o break.

PD ¡Gracias por leer! ¡Más consejos próximamente! Un agradecimiento especial a SonarQube y sus reglas - https://www.sonarqube.org/