Problemas de nombres: ¿Debería cambiarse el nombre de "ISomething" por "Something"? [cerrado]

67
El capítulo

del tío Bob sobre los nombres en Código limpio recomienda evitar codificaciones en los nombres, principalmente en lo que respecta a la notación húngara. También menciona específicamente la eliminación del prefijo I de las interfaces, pero no muestra ejemplos de esto.

Supongamos 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 sola interfaz con un solo 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?

    
pregunta Vinko Vrsalovic 23.08.2016 - 11:01

8 respuestas

191

Si bien muchos, incluido el "Tío Bob", recomiendan 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ás escribiendo C #, deberías seguir las convenciones de ese idioma y usarlo. No hacerlo causará una gran confusión con cualquier persona familiarizada con C # que intente leer su código.

    
respondido por el David Arno 23.08.2016 - 11:48
39

La interfaz es el concepto lógico importante, por lo tanto, la interfaz debe llevar el nombre genérico. Por lo tanto, prefiero tener

interface Something
class DefaultSomething : Something
class MockSomething : Something

que

interface ISomething
class Something : ISomething
class MockSomething : ISomething

Este último tiene varias cuestiones:

  1. Algo es solo una implementación de ISomething, pero es la que tiene el nombre genérico, como si fuera algo especial.
  2. MockSomething parece derivar de Something, pero implementa ISomething. Tal vez debería llamarse MockISomething, pero nunca lo he visto en la naturaleza.
  3. Refactorizar es más difícil. Si Algo es una clase ahora, y luego descubre que tiene que introducir una interfaz, esa interfaz debería tener el mismo nombre, ya que debería ser transparente para los clientes si el tipo es una clase concreta, una clase abstracta o una interfaz. . ISomething rompe eso.
respondido por el wallenborn 23.08.2016 - 12:30
27

Esto no es solo acerca de las convenciones de nombres. C # no admite la herencia múltiple, por lo que este uso heredado de la notación húngara tiene un pequeño pero útil beneficio cuando se hereda de una clase base e implementa una o más interfaces. Así que esto ...

class Foo : BarB, IBarA
{
    // Better...
}

... es preferible a esto ...

class Foo : BarB, BarA
{
    // Dafuq?
}

IMO

    
respondido por el Robbie Dee 23.08.2016 - 16:55
15

No, no, no.

Las convenciones de nomenclatura no son una función de tu fandom de Uncle Bob. No son una función del idioma, 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.

Sólo entonces tienes que decidir. Después de eso solo sé consistente. Si alguien comienza a ser inconsistente, retire su pistola nerf y hágales actualizar la documentación hasta que se arrepientan.

Al leer una nueva base de código, si nunca veo un prefijo I , 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 cliente, me reservo el derecho de no importarme con lo que estoy hablando. Todo lo que necesito es un nombre y una lista de cosas a las que puedo llamar en contra de ese nombre. Esos no pueden cambiar a menos que yo lo diga. YO SOY LA PARTE Con lo que estoy hablando, con interfaz o no, no es mi departamento.

Si escondes un I en ese nombre, al cliente no le importa. Si no hay I , al cliente no le importa. Lo que está hablando podría ser concreto. Puede que no. Como no llama a new en ninguna forma, no hace ninguna diferencia. Como cliente, no lo sé. No quiero saber.

Ahora, 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 avisar al cliente. Si no existe una tradición I , es posible que ni siquiera le cambie el nombre. Lo que podría ser un problema porque ahora tenemos que volver a compilar al cliente porque a pesar de que a la fuente no le importa, al binario no le importa. Algo fácil de perder si nunca cambiaste el nombre.

Tal vez eso no sea un problema para ti. Tal vez no. Si lo es, esa es una buena razón para preocuparse. Pero por amor a los juguetes de escritorio, no debemos decir que deberíamos hacer esto ciegamente porque así se hace en algunos idiomas. O bien tienes una buena razón o no te importa.

No se trata de vivir con ella para siempre. Se trata de no darme una mierda sobre el código base que no se ajusta a tu mentalidad particular a menos que estés preparado para solucionar el "problema" en todas partes. A menos que tenga una buena razón para no tomar el camino de menor resistencia a la uniformidad, no se me acerque a predicar la conformidad. Tengo mejores cosas que hacer.

    
respondido por el candied_orange 24.08.2016 - 03:37
8

Hay una pequeña diferencia entre Java y C # que es relevante aquí. En Java, cada miembro es virtual por defecto. En C #, todos los miembros están sellados de forma predeterminada, excepto los miembros de la interfaz.

Las suposiciones que acompañan a esto influyen en la guía: en Java, todo tipo de público debe considerarse no definitivo, de acuerdo con el Principio de Sustitución de Liskov [1]. Si solo tienes una implementación, nombrarás la clase Parser ; Si descubre que necesita varias implementaciones, 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. Tenga en cuenta que esto no es en absoluto un 100% exacto: un contraejemplo típico sería clases como Stream (que realmente debería haber sido una interfaz, o un par de interfaces), y cada uno tiene sus propias directrices y antecedentes en otros idiomas. . También hay otras excepciones, como el sufijo Base , ampliamente utilizado para denotar una clase abstracta: al igual que con una interfaz, sabes el tipo se supone que 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 múltiple de clase en DO#). Esto fue popularizado por LINQ, que usa IEnumerable<T> como interfaz, y Enumerable como repositorio de métodos que se aplican a esa interfaz. Esto no es necesario en Java, donde las interfaces también pueden contener implementaciones de métodos.

En última instancia, el prefijo I se usa ampliamente en el mundo C # y, por extensión, el mundo .NET (ya que la mayor parte del código .NET está escrito en C #, tiene sentido seguir las directrices de C # para la mayoría de las interfaces públicas). Esto significa que es casi seguro que trabajará con las bibliotecas y el código que sigue a esta notación, y tiene sentido adoptar la tradición para evitar confusiones innecesarias. No es como omitir el prefijo que mejorará su código :)

Supongo que el razonamiento del tío Bob fue algo como esto:

IBanana es la noción abstracta de banano. Si puede haber alguna clase de implementación que no tenga un nombre mejor que Banana , la abstracción no tiene ningún significado, y usted debe descartar la interfaz y solo usar una clase. Si hay un nombre mejor (por ejemplo, LongBanana o AppleBanana ), no hay razón para no usar Banana como el nombre de la interfaz. Por lo tanto, usar el prefijo I 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 como el OOP estricto tendrá que codificar contra las interfaces, el único lugar donde no vería el prefijo I en un tipo sería en un constructor: un ruido bastante inútil.

Si aplica esto a su interfaz IParser de muestra, puede ver claramente que la abstracción está completamente en el territorio "sin significado". O hay algo específico acerca de una implementación concreta de un analizador (por ejemplo, JsonParser , XmlParser , ...), o simplemente debes usar una clase. No existe tal cosa como "implementación predeterminada" (aunque en algunos entornos, esto sí tiene sentido, especialmente COM, ya sea una implementación específica o desea 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 el prefijo I , consérvelo. Solo haga una nota mental cada vez que vea un código como class Something: ISomething ; significa que alguien no es muy bueno en seguir a YAGNI y construir abstracciones razonables.

[1] - Técnicamente, esto no se menciona específicamente en el artículo de Liskov, pero es uno de los cimientos del documento original de OOP y en mi lectura de Liskov, ella no desafió esto. En una interpretación menos estricta (la que se toma en 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.

    
respondido por el Luaan 24.08.2016 - 11:17
4
  

Entonces, por ejemplo, ¿cómo deberían llamarse estos dos? Analizador y ConcreteParser? Parser y ParserImplementation?

En un mundo ideal, no debes prefijar tu nombre con una mano corta ("I ..."), sino simplemente dejar que el nombre exprese un concepto.

Leí en algún lugar (y no puedo encontrarlo) la opinión de que esta convención de ISomething te lleva a definir un concepto "IDollar" cuando necesitas una clase de "Dólar", pero la solución correcta sería un concepto llamado simplemente "Moneda".

En otras palabras, la convención da una fácil explicación a la dificultad de nombrar cosas y la salida fácil es sutilmente errónea .

En el mundo real, los clientes de su código (que pueden 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) ( proporcione a los clientes de su código lo que esperan obtener).

La convención de 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 "usar ISomething", entonces es mejor usarlo, para cumplir (y funcionar mejor) con otros desarrolladores.

*) Puedo decir que esto no se debe a que "cruft" no es un concepto exacto de todos modos :)

    
respondido por el utnapistim 24.08.2016 - 12:02
2

Como mencionan las otras respuestas, el prefijo de sus nombres de interfaz con I es parte de las pautas de codificación para el marco .NET. Debido a que las clases concretas interactúan con más frecuencia que las interfaces genéricas en C # y VB.NET, son de primera clase en los esquemas de nomenclatura y, por lo tanto, deberían tener los nombres más simples, por lo tanto, IParser para la interfaz y Parser para 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 el I debe eliminarse y las interfaces deben tener los nombres más simples: Parser para la interfaz del analizador , por ejemplo. Pero en lugar de un nombre basado en roles como ParserDefault , la clase debe tener un nombre que describa cómo el analizador está implementado :

public interface Parser{
}

public class RegexParser implements Parser {
    // parses using regular expressions
}

public class RecursiveParser implements Parser {
    // parses using a recursive call structure
}

public class MockParser implements Parser {
    // BSes the parsing methods so you can get your tests working
}

Esto es, por ejemplo, cómo se denominan Set<T> , HashSet<T> y TreeSet<T> .

    
respondido por el TheHansinator 23.08.2016 - 19:19
-8

Tienes razón en que esta convención I = interface rompe las (nuevas) reglas

En los viejos tiempos, prefijaría todo con su tipo intCounter boolFlag, etc.

Si desea nombrar cosas sin el prefijo, pero también evitar 'ConcreteParser' podría usar espacios de nombres

namespace myapp.Interfaces
{
    public interface Parser {...
}


namespace myapp.Parsers
{
    public class Parser : myapp.Interfaces.Parser {...
}
    
respondido por el Ewan 23.08.2016 - 11:11

Lea otras preguntas en las etiquetas