martes, 20 de enero de 2015
Hace relativamente poco tiempo, y como parte del popular salto al open source del nuevo stack de Microsoft, se publicaron en Github las directrices o normas de codificación que deben seguirse para contribuir en el desarrollo de .NET Core. Se trata de un resumen simplificado del contenido del libro “Framework design guidelines” publicado por Krzysztof Cwalina y Brad Abrams en 2008.
Aunque creo que este mismo documento o versiones anteriores lo he leído en otras ocasiones, la verdad es que sigue resultándome muy interesante porque me recuerda normas y convenciones que conviene tener en cuenta al desarrollar marcos de trabajo y componentes. No hay que olvidar que estas directrices han sido refinadas y mejoradas con los años, acumulando ya la experiencia de muchos años y muchos desarrolladores que han trabajado en .NET framework, así que se trata de una base de conocimiento nada despreciable. De hecho, estas pautas son las bases del diseño de frameworks dentro de la propia Microsoft.
Y como a menudo me encuentro con equipos que no tienen guías de estilo ni convenciones de codificación formalizadas, he pensado que quizás sería interesante traducirlas para que puedan ser usadas como punto de partida para crear sus propias normas y, en cualquier caso, siempre dan buenas ideas que podemos aplicar en el día a día para hacer mejor nuestros componentes.
Es una lectura cortita, ¡que aproveche!
Por ejemplo, si diseñas un API que permita tiempos, podrías escribir el siguiente código de ejemplo para los escenarios:
¡Y eso es todo! Claramente hay algunas normas a las que se les nota el paso del tiempo, e incluso puede haber alguna discutible, pero espero que os resulten interesantes y os sean útiles como base si aún no tenéis directrices claras. Y si queréis contribuir al desarrollo de .NET, son las reglas a las que hay que ceñirse ;)
Publicado en Variable not found.
Aunque creo que este mismo documento o versiones anteriores lo he leído en otras ocasiones, la verdad es que sigue resultándome muy interesante porque me recuerda normas y convenciones que conviene tener en cuenta al desarrollar marcos de trabajo y componentes. No hay que olvidar que estas directrices han sido refinadas y mejoradas con los años, acumulando ya la experiencia de muchos años y muchos desarrolladores que han trabajado en .NET framework, así que se trata de una base de conocimiento nada despreciable. De hecho, estas pautas son las bases del diseño de frameworks dentro de la propia Microsoft.
Y como a menudo me encuentro con equipos que no tienen guías de estilo ni convenciones de codificación formalizadas, he pensado que quizás sería interesante traducirlas para que puedan ser usadas como punto de partida para crear sus propias normas y, en cualquier caso, siempre dan buenas ideas que podemos aplicar en el día a día para hacer mejor nuestros componentes.
Es una lectura cortita, ¡que aproveche!
1. Principios generales de diseño
1.1. Diseño guiado por escenarios
Comienza el proceso de diseño de tu API público definiendo los escenarios más importantes de cada área funcional. Escribe el código que te gustaría que escribieran los usuarios finales cuando vayan a implementar esos escenarios usando tu API. Después, diseña tu API basándote en el código de ejemplo que escribiste.Por ejemplo, si diseñas un API que permita tiempos, podrías escribir el siguiente código de ejemplo para los escenarios:
// Escenario #1 : medir el tiempo transcurrido Stopwatch watch = Stopwatch.StartNew(); DoSomething(); Console.WriteLine(watch.Elapsed); // Escenario #2 : reutilizar el cronómetro Dim watch As Stopwatch = Stopwatch.StartNew() DoSomething(); Console.WriteLine(watch.Elapsed) watch.Reset(); watch.Start(); DoSomething() Console.WriteLine(watch.Elapsed) // Escenario #3: ...
1.2. Estudios de usabilidad
Realiza pruebas de usabilidad de tu API. Haz que desarrolladores que no estén familiarizados con tu API implementen los principales escenarios, e intenta identificar qué partes no son intuitivas.1.3. API auto-documentada
Los desarrolladores que utilicen tu API deberían ser capaces de implementar los principales escenarios sin leer la documentación. Ayuda a los usuarios a descubrir qué tipos y métodos necesitan utilizar dándoles nombres intuitivos para los tipos y miembros más frecuentes. Hablad sobre la elección de los nombres durante las revisiones.1.4. Entiende a tu cliente
Ten en cuenta que la mayoría de tus clientes no son como tú. Deberías diseñar el API para tus clientes, no para los desarrolladores más cercanos, que, a diferencia de tus clientes, son expertos en la tecnología que estás intentando exponer.2. Reglas de nombrado
El uso de mayúsculas y minúsculas sólo deben ser tenidas en cuenta en identificadorespublic
y protected
, así como en implementaciones privadas de miembros de interfaz. Sin embargo, los equipos de trabajo pueden elegir libremente sus propio estilo para los identificadores internal
y private
.- Utiliza “PascalCasing” (primera letra de cada palabra en mayúsculas) para todos los identificadores excepto para los nombre de parámetro. Por ejemplo, usa
en vez deTextColor
oTextcolor
.Text color
- Utiliza “camelCasing” (primera letra de cada palabra excepto la primera en mayúsculas) para todos los nombres de parámetro. Precede los nombres de parámetros genéricos con la letra “T”:
public interface ISessionChannel<TSession> where TSession : ISession { TSession Session { get; } }
- Considera utilizar simplemente T como nombre de parámetro genérico de una única letra.
- Utiliza “PascalCasing” o “camelCasing” para acrónimos con más de dos caracteres. Por ejemplo, utiliza
HtmlButton
en lugar deHTMLButton
, peroSystem.IO
en lugar deSystem.Io
. - No uses acrónimos que no estén aceptados generalmente en el campo o contexto de la aplicación.
- Utiliza acrónimos bien conocidos sólo cuando sean absolutamente necesarios. Por ejemplo, puedes utilizar
UI
para user interface (interfaz de usuario) oHtml
para Hyper-Text Markup Language (lenguaje de marcas hipertexto). - No uses contracciones como partes de nombres de identificador. Por ejemplo, usa
GetWindow
en lugar deGetWin
. - No uses guiones, ni guiones bajos ni cualquier otro tipo de caracteres alfanuméricos.
- No uses la Notación Húngara.
- Nombra tipos y propiedades usando sustantivos o frases nominales.
- Nombra métodos y eventos con verbos o frases verbales. Para los eventos, elije siempre nombres que indiquen si se ha lanzado antes o después de que la acción se produzca utilizando el gerundio y el pasado perfecto, respectivamente. Por ejemplo, si un evento es lanzado antes de cerrar un formulario, debería llamarse
Closing
; si es lanzado después de que sea cerrado, debería llamarseClosed
. - No utilices prefijos “Before” o “After” en el nombre de los eventos para indicar si la acción se realizó o no (cíñete al punto anterior).
- Utiliza
I
como prefijo para todos los nombres de interfaz. - Utiliza las siguientes terminaciones:
Exception
para tipos que hereden deSystem.Exception
.Collection
para tipos que implementenIEnumerable
.Dictionary
para tipos que implementenIDictionary
oIDictionary<K,V>
.EventArgs
para tipos que hereden deSystem.EventArgs
.EventHandler
para tipos que hereden deSystem.Delegate
.Attribute
para tipos que hereden deSystem.Attribute
.
- No utilices las terminaciones anteriores para ningún otro tipo.
- No uses como terminación de nombres las palabras
Flags
oEnum
. - Utiliza nombres o frases nominales en plural para las enumeraciones de flags (las que soportan operaciones de bit) y en singular para las enumeraciones normales.
- Utiliza el siguiente esquema para los espacios de nombres:
<Company>.<Technology>[.<Feature>].
Un ejemplo seríaMicrosoft.Office.ClipGallery
. Sin embargo, los componentes de sistema operativo deberían usar espacios de nombres comenzando porSystem
. - No utilices jerarquías de la organización para organizar los espacios de nombre. Éstos deben corresponder con escenarios, independientemente de los equipos que hayan creado las APIs.
3. Pautas generales de diseño
- Usa el tipo más específico para los valores de retorno, y el menos específico para los parámetros de entrada. Por ejemplo, acepta
IEnumerable
como parámetro de entrada, pero retornaCollection<string>
. - Provee de un claro punto de entrada a tu API para cada escenario. Cada área funcional debería tener preferiblemente uno, aunque aunque a veces sean más, tipos que actúan como puntos de entrada para explorar la tecnología. Lo llamamos “componentes agregados”. La implementación de la gran mayoría de escenarios en una tecnología determinada deberían comenzar desde uno de esos componentes agregados.
- Escribe código de ejemplo para los escenarios de uso más frecuentes. El primer tipo usado en esos ejemplos debería ser un componente agregado y el código debería ser trivial. Si el código va más allá de unas cuantas líneas, necesitas rediseñarlo. Por ejemplo, escribir a un log de eventos en el API Win32 tomaba alrededor de 100 líneas de código; en .NET, escribir al EventLog necesita una única línea de código.
- Modela en componentes agregados los conceptos de mayor nivel (objetos físicos) en lugar de tareas a nivel de sistema. For example
File
,Directory o
Drive
son más sencillos de comprender y utilizar queStream
,Formatter o
Comparer
. - No obligues a que los usuarios de tus APIs instancien múltiples objetos en los principales escenarios. Las tareas simples deberían hacerse con un único
new
. - Permite que tus APIs sean usadas mediante el estilo “crear-asignar-llamar” en todos los componentes agregados. Debería ser posible instanciar todos los componentes con el constructor por defecto, establecer una o más propiedades y llamar a métodos o responder a eventos:
var applicationLog = new EventLog(); applicationLog.Source = "MySource"; applicationLog.WriteEntry(exception.Message);
- No requieras demasiadas inicializaciones antes de que los componentes agregados puedan ser utilizados. Si alguna inicialización es necesaria, la excepción lanzada cuando el componente no haya sido correctamente inicializado debería explicar claramente qué hay que hacer para que funcione.
- Elije cuidadosamente los nombres para tus tipos, métodos y parámetros. Dedica tiempo a pensar cuál es el primer nombre que la gente intentará teclear cuando esté explorando tu API, y resérvalo para tu componente agregado. Un error común es usar el “mejor” nombre para un tipo base. Pasa FxCop a tus bibliotecas.
- Asegúrate de que tu biblioteca es CLS compliant. Aplica
CLSCompliantAttribute
a tus ensamblados. - Ten preferencia por usar clases en lugar de interfaces.
- No crees tipos sellados a menos que tengas buenas razones para hacerlo.
- No crees tipos valor mutables.
- No proveas abstracciones como interfaces o clases abstractas sin proveer al menos de un tipo concreto que implemente cada abstracción. Esto ayudará a validar el diseño.
- No proveas interfaces sin disponer al menos de un API que lo consuma, es decir, un método que requiera un parámetro del tipo del interfaz. Esto ayudará a validar el diseño del interfaz.
- Evita crear tipos anidados públicos.
- Aplica el atributo
FlagsAttribute
a enumeraciones de flags. - Utiliza principalmente colecciones en lugar de arrays en los APIs públicos.
- No utilices
ArrayList
,List<T>
,Hashtable
, oDictionary<K,V>
in APIs públicas. Utiliza en su lugarCollection<T>
,ReadOnlyCollection<T>
,KeyedCollection<K,V>
, o los subtipos deCollectionBase
instead. Ten en cuenta que las colecciones genéricas son sólo soportadas en .NET framework 2.0 o superior. - No utilices códigos de error para notificar fallos. Usa excepciones en su lugar.
- No lances excepciones de tipo
Exception
oSystemException
. - Evita capturar excepciones del tipo base
Exception
. - Opta preferiblemente por lanzar excepciones ya existentes de propósito general como
ArgumentNullException
,ArgumentOutOfRangeException o
InvalidOperationException
en lugar de definir tus propias excepciones. Lanza siempre la excepción más específica posible. - Asegúrate de que los mensajes de excepción son claros y aplicables.
- Utiliza
EventHandler<T>
para eventos en lugar de definir manualmente manejadores de delegados. - Usa preferiblemente APIs basadas en eventos en lugar de usar delegados.
- Usa preferiblemente constructores en lugar de métodos de factoría.
- No expongas directamente campos públicos. Utiliza propiedades en su lugar.
- Usa preferiblemente propiedades para almacenamiento, pero utiliza métodos en los siguientes casos:
- Cuando la operación sea una conversión (como en
Object.ToString()
) . - Cuando la operación puede ser costosa (órdenes de magnitud más lento de lo que podría ser el establecimiento de un campo).
- Cuando el hecho de obtener el valor de una propiedad utilizando el getter tenga un efecto lateral observable.
- Cuando llamar varias veces consecutivas al miembro pudiera retornar distintos resultados.
- Cuando el miembro retorne un array. Nota: los miembros que retornen un array deberían retornar copias del array interno, no una referencia al mismo.
- Cuando la operación sea una conversión (como en
- Permite que las propiedades sean establecidas en cualquier orden. Las propiedades deberían ser stateless respecto a otras propiedades.
- No hagas que un miembro sea virtuales a menos que tengas razones de peso para hacerlo.
- Evita los finalizadores de clases.
- Implementa
IDisposable
en todos los tipos que adquieran recursos nativos, así como en los que dispongan de finalizadores. - Sé consistente en el orden y nombrado de los parámetros de métodos. Es frecuente encontrar métodos sobrecargados con un número creciente de parámetros para permitir al desarrollador especificar el nivel de información que necesite.
- Asegúrate de que todas las sobrecargas muestran parámetros consistentes en su orden (el mismo parámetro se encuentra en el mismo lugar en la signatura) y nombre. Entre las sobrecargas, el único método que debería ser virtual es el que tiene más parámetros y sólo cuando la extensibilidad es necesaria.
public class Foo { private readonly string _defaultForA = "default value for a"; private readonly int _defaultForB = 42; public void Bar() { Bar(_defaultForA, _defaultForB); } public void Bar(string a) { Bar(a, _defaultForB); } public void Bar(string a, int b) { // core implementation here } }
- Evita el uso de parámetros
out
yref
.
¡Y eso es todo! Claramente hay algunas normas a las que se les nota el paso del tiempo, e incluso puede haber alguna discutible, pero espero que os resulten interesantes y os sean útiles como base si aún no tenéis directrices claras. Y si queréis contribuir al desarrollo de .NET, son las reglas a las que hay que ceñirse ;)
Publicado en Variable not found.
7 Comentarios:
Buen articulo
El orden es vital para que las cosas vayan bien
No sé si está al día este post, pero me llama la atención que uno de los puntos sea:
No uses guiones, ni guiones bajos ni cualquier otro tipo de caracteres alfanuméricos.
Y en algún ejemplo (y en muchos proyectos que veo), aparecen variables comenzando con guiones bajos. ¿qué tipo de convención es?
Hola!
Sí, parece que las convenciones han evolucionado algo en los últimos meses. Ahora están actualizadas aquí:
https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md.
Saludos!
Hola!
Soy novatillo y no pillo la necesidad de un tipo anónimo en el ejemplo:
public interface ISessionChannel
where TSession : ISession
{
TSession Session { get; }
}
No es lo mismo que:
public interface ISessionChannel
{
ISession Session { get; }
}
Saludos.
Anónimo Anónimo dijo...
Hola!
Se come los menor y mayor que (los sustituyo por la @)
public interface ISessionChannel@TSession@
where TSession : ISession
{
TSession Session { get; }
}
¿No es lo mismo que?:
public interface ISessionChannel@ISession@
{
ISession Session { get; }
}
Saludos.
Hola!
Buff, se me coló el where en el ejemplo anterior.
Hola!
Bueno, al final creo que no he podido leer bien el tema, pero supongo que te refieres a los tipos parametrizados o generics.
La diferencia entre esos dos ejemplos es que, en el segundo de ellos, la propiedad Session debe ser siempre ISession, mientras que usando un parámetro genérico <T> (como en tu primer ejemplo) abres la posibilidad de que sea cualquier tipo T. Al final, los genéricos lo que te dan es una flexibilidad mucho mayor a la hora de reutilizar tus clases, interfaces o métodos.
Saludos!
Enviar un nuevo comentario