El capítulo del tío Bob sobre nombres en Clean Code recomienda evitar las codificaciones en los nombres, principalmente en relación con la notación húngara. También menciona específicamente la eliminación del I
prefijo de las interfaces, pero no muestra ejemplos de esto.
Asumamos lo siguiente:
- El uso de la interfaz es principalmente para lograr la capacidad de prueba a través de la inyección de dependencia
- En muchos casos, esto lleva a tener una única interfaz con un único implementador
Entonces, por ejemplo, ¿cómo deberían llamarse estos dos? Parser
y ConcreteParser
? Parser
y ParserImplementation
?
public interface IParser {
string Parse(string content);
string Parse(FileInfo path);
}
public class Parser : IParser {
// Implementations
}
¿O debería ignorar esta sugerencia en casos de implementación única como este?
c#
naming
clean-code
naming-standards
Vinko Vrsalovic
fuente
fuente
Respuestas:
Mientras que muchos, incluido el "Tío Bob", aconsejan no usar
I
como prefijo para las interfaces, hacerlo es una tradición bien establecida con C #. En términos generales, debe evitarse. Pero si está escribiendo C #, realmente debe seguir las convenciones de ese lenguaje y usarlo. No hacerlo causará una gran confusión con cualquier otra persona familiarizada con C # que intente leer su código.fuente
La interfaz es el concepto lógico importante, por lo tanto, la interfaz debe llevar el nombre genérico. Entonces, prefiero tener
que
Este último tiene varias cuestiones:
fuente
Default
oReal
oImpl
en muchas de mis nombres de claseIList<T>
y realmente no le importa cómo se almacena internamente; ser capaz de simplemente cortarI
y tener un nombre de clase utilizable parece más agradable que tener que saber mágicamente (como en Java) que el código que desea una implementación ordinariaList<T>
debería usarArrayList<T>
.CFoo : Foo
. Entonces esa opción no está realmente disponible. Dos, obtienes una extraña inconsistencia porque algunas clases tendrían el marcador y otras no, dependiendo de si podría haber otras implementaciones de la misma interfaz.CFoo : Foo
peroSqlStore : Store
. Ese es un problema que tengoImpl
, que es esencialmente solo un marcador más feo. Tal vez si hubiera un lenguaje con la convención de agregar siempre unC
(o lo que sea), eso podría ser mejor queI
Esto no se trata solo de nombrar convenciones. C # no admite la herencia múltiple, por lo que este uso heredado de la notación húngara tiene un pequeño beneficio, aunque útil, cuando hereda de una clase base e implementa una o más interfaces. Así que esto...
... es preferible a esto ...
OMI
fuente
inherits
vsimplements
.extends
.No no no.
Las convenciones de nomenclatura no son una función de tu tío Bob fandom. No son una función del lenguaje, c # o de otra manera.
Son una función de su base de código. En mucho menor medida de su tienda. En otras palabras, el primero en la base del código establece el estándar.
Solo entonces puedes decidir. Después de eso, solo sé consistente. Si alguien comienza a ser inconsistente, quíteles su arma nerf y haga que actualicen la documentación hasta que se arrepientan.
Cuando leo una nueva base de código si nunca veo un
I
prefijo, estoy bien, independientemente del idioma. Si veo uno en cada interfaz, estoy bien. Si a veces veo uno, alguien va a pagar.Si se encuentra en el contexto enrarecido de establecer este precedente para su base de código, le insto a que considere esto:
Como clase de cliente, me reservo el derecho de no importarme lo que esté hablando. Todo lo que necesito es un nombre y una lista de cosas a las que pueda llamar contra ese nombre. Esos no pueden cambiar a menos que yo lo diga. Soy dueño de esa parte. Lo que estoy hablando, interfaz o no, no es mi departamento.
Si se cuela
I
con ese nombre, al cliente no le importa. Si no hayI
, al cliente no le importa. Con lo que está hablando podría ser concreto. Puede que no. Como no lo llamanew
de ninguna manera, no hay diferencia. Como cliente, no lo sé. No quiero saberAhora, como un mono de código que mira ese código, incluso en Java, esto podría ser importante. Después de todo, si tengo que cambiar una implementación a una interfaz, puedo hacerlo sin decirle al cliente. Si no hay
I
tradición, ni siquiera podría cambiarle el nombre. Lo que podría ser un problema porque ahora tenemos que volver a compilar el cliente porque, aunque a la fuente no le importa, el binario sí lo hace. Algo fácil de perder si nunca cambiaste el nombre.Tal vez eso no sea un problema para ti. Tal vez no. Si es así, esa es una buena razón para preocuparse. Pero por amor a los juguetes de escritorio, no digamos que deberíamos hacer esto a ciegas porque es la forma en que se hace en algún idioma. O tienes una muy buena razón o no te importa.
No se trata de vivir con eso para siempre. Se trata de no darme una mierda sobre la base del código que no se ajusta a su mentalidad particular a menos que esté preparado para solucionar el 'problema' en todas partes. A menos que tenga una buena razón por la que no debería tomar el camino de menor resistencia a la uniformidad, no venga a predicar conformidad. Tengo mejores cosas que hacer.
fuente
I
s. Si su código los usa, los verá en todas partes. Si su código no los usa, los verá desde el código marco, lo que lleva exactamente a la mezcla que no desea.Hay una pequeña diferencia entre Java y C # que es relevante aquí. En Java, cada miembro es virtual por defecto. En C #, cada miembro está sellado de manera predeterminada, excepto los miembros de la interfaz.
Los supuestos que acompañan a esto influyen en la directriz: en Java, cada tipo público debe considerarse no final, de acuerdo con el Principio de sustitución de Liskov [1]. Si solo tiene una implementación, nombrará la clase
Parser
; si encuentra que necesita implementaciones múltiples, simplemente cambiará la clase a una interfaz con el mismo nombre y cambiará el nombre de la implementación concreta a algo descriptivo.En C #, la suposición principal es que cuando obtienes una clase (el nombre no comienza con
I
), esa es la clase que deseas. Eso sí, esto no está cerca del 100% de precisión: un contraejemplo típico sería clases comoStream
(que realmente deberían haber sido una interfaz, o un par de interfaces), y todos tienen sus propias pautas y antecedentes de otros idiomas. También hay otras excepciones, como elBase
sufijo bastante utilizado para denotar una clase abstracta: al igual que con una interfaz, sabes que se supone que el tipo es polimórfico.También hay una buena característica de usabilidad al dejar el nombre sin prefijo I para la funcionalidad que se relaciona con esa interfaz sin tener que recurrir a hacer de la interfaz una clase abstracta (lo que perjudicaría debido a la falta de herencia de clase múltiple en C #). Esto fue popularizado por LINQ, que utiliza
IEnumerable<T>
como interfaz yEnumerable
como depósito de métodos que se aplican a esa interfaz. Esto es innecesario en Java, donde las interfaces también pueden contener implementaciones de métodos.En última instancia, el
I
prefijo se usa ampliamente en el mundo de C # y, por extensión, en el mundo de .NET (dado que la mayoría del código de .NET está escrito en C #, tiene sentido seguir las pautas de C # para la mayoría de las interfaces públicas). Esto significa que seguramente trabajará con bibliotecas y códigos que siguen esta notación, y tiene sentido adoptar la tradición para evitar confusiones innecesarias; no es como omitir el prefijo mejorará su código :)Supongo que el razonamiento del tío Bob fue algo como esto:
IBanana
Es la noción abstracta de plátano. Si puede haber una clase de implementación que no tenga un nombre mejor que elBanana
, la abstracción no tiene ningún sentido, y debe abandonar la interfaz y simplemente usar una clase. Si hay un nombre mejor (digamos,LongBanana
oAppleBanana
), no hay razón para no usarloBanana
como nombre de la interfaz. Por lo tanto, usar elI
prefijo significa que tiene una abstracción inútil, lo que hace que el código sea más difícil de entender sin ningún beneficio. Y dado que la OOP estricta siempre tendrá que codificar contra interfaces, el único lugar donde no vería elI
prefijo de un tipo sería en un constructor, un ruido bastante inútil.Si aplica esto a su
IParser
interfaz de muestra , puede ver claramente que la abstracción está completamente en el territorio "sin sentido". O bien hay algo específico acerca de una aplicación concreta de un programa de análisis (por ejemploJsonParser
,XmlParser
, ...), o que sólo debe utilizar una clase. No existe tal cosa como "implementación predeterminada" (aunque en algunos entornos, esto tiene sentido, especialmente COM), ya sea que haya una implementación específica o que desee una clase abstracta o métodos de extensión para los "valores predeterminados". Sin embargo, en C #, a menos que su base de código ya omita elI
prefijo, guárdelo. Solo toma una nota mental cada vez que veas un códigoclass Something: ISomething
, significa que alguien no es muy bueno siguiendo a YAGNI y construyendo abstracciones razonables.[1] - Técnicamente, esto no se menciona específicamente en el documento de Liskov, pero es uno de los fundamentos del documento original de OOP y en mi lectura de Liskov, ella no cuestionó esto. En una interpretación menos estricta (la que toman la mayoría de los lenguajes OOP), esto significa que cualquier código que use un tipo público destinado a la sustitución (es decir, no final / sellado) debe funcionar con cualquier implementación conforme de ese tipo.
fuente
En un mundo ideal, no debe anteponer su nombre con una abreviatura ("I ...") sino simplemente dejar que el nombre exprese un concepto.
Leí en alguna parte (y no puedo encontrarlo) la opinión de que esta convención ISomething lo lleva a definir un concepto "IDollar" cuando necesita una clase "Dólar", pero la solución correcta sería un concepto llamado simplemente "Moneda".
En otras palabras, la convención facilita la dificultad de nombrar cosas y la salida es sutilmente incorrecta .
En el mundo real, los clientes de su código (que puede ser "usted del futuro") deben poder leerlo más tarde y familiarizarse con él lo más rápido (o con el menor esfuerzo) posible (entregue a los clientes de su código lo que esperan obtener).
La convención ISomething, introduce cruft / bad names. Dicho esto, el cruft no es real cruft *), a menos que las convenciones y prácticas utilizadas para escribir el código ya no sean reales; si la convención dice "use ISomething", entonces es mejor usarlo, conformarse (y funcionar mejor) con otros desarrolladores.
*) Me salgo con la suya porque "cruft" no es un concepto exacto de todos modos :)
fuente
Como mencionan las otras respuestas, el prefijo de los nombres de su interfaz
I
es parte de las pautas de codificación para el marco .NET. Debido a que las clases concretas se interactúan con más frecuencia que las interfaces genéricas en C # y VB.NET, son de primera clase en esquemas de nomenclatura y, por lo tanto, deben tener los nombres más simples,IParser
por lo tanto, para la interfaz yParser
la implementación predeterminada.Pero en el caso de Java y lenguajes similares, donde se prefieren las interfaces en lugar de las clases concretas, el tío Bob tiene razón en que
I
deben eliminarse y las interfaces deben tener los nombres más simplesParser
, por ejemplo, para su interfaz de analizador. Pero en lugar de un nombre basado en roles comoParserDefault
, la clase debería tener un nombre que describa cómo se implementa el analizador :Este es, por ejemplo, cómo
Set<T>
,HashSet<T>
yTreeSet<T>
se nombran.fuente
sealed
por defecto, y debe hacerlos explícitamentevirtual
. Ser virtual es el caso especial en C #. Por supuesto, a medida que el enfoque recomendado para escribir código cambió a lo largo de los años, muchas reliquias del pasado debían permanecer por compatibilidad :) El punto clave es que si obtienes una interfaz en C # (que comienza conI
), sabes que es completamente tipo abstracto; si obtiene una no interfaz, la suposición típica es que ese es el tipo que desea, maldita sea LSP.Tienes razón en que esta convención de interfaz I = rompe las reglas (nuevas)
En los viejos tiempos, prefijaste todo con su tipo intCounter boolFlag, etc.
Si desea nombrar cosas sin el prefijo, pero también evitar 'ConcreteParser', puede usar espacios de nombres
fuente
intCounter
oboolFlag
. Esos son los "tipos" incorrectos. En su lugar, escribiríapszName
(puntero a una cadena terminada en NUL que contiene un nombre),cchName
(recuento de caracteres en la cadena "nombre"),xControl
(coordenada x del control),fVisible
(indicador que indica que algo es visible),pFile
(puntero a un archivo),hWindow
(manejar a una ventana), y así sucesivamente.xControl
acchName
. Ambos son de tipoint
, pero tienen una semántica muy diferente. Los prefijos hacen que esta semántica sea obvia para un humano. Un puntero a una cadena terminada en NUL se ve exactamente como un puntero a una cadena de estilo Pascal a un compilador. Del mismo modo, elI
prefijo para las interfaces surgió en el lenguaje C ++ donde las interfaces son realmente solo clases (y de hecho para C, donde no existe una clase o una interfaz a nivel del lenguaje, todo es solo una convención).