Problemas de nomenclatura: ¿Se debe cambiar el nombre de "ISomething" a "Something"? [cerrado]

68

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 Iprefijo 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? Parsery ConcreteParser? Parsery 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?

Vinko Vrsalovic
fuente
30
Eso no lo hace menos una religión. Debes buscar razones, no solo alguien X dice Y.
Mike Dunlavey
35
@MikeDunlavey ¡Y esa es la razón de la pregunta!
Vinko Vrsalovic
14
Si hay un cuerpo de código existente (que no va a reescribir), entonces quédese con cualquier convención que use. Si se trata de un código nuevo, use lo que la mayoría de las personas que van a trabajar en él estén más felices o acostumbrados. Solo si no se aplica lo anterior, debería convertirse en una cuestión filosófica.
TripeHound
9
La regla de mayoría de @TripeHound no siempre es la mejor opción. Si hay razones por las que algunas cosas son mejores que otras, una sola persona puede convencer al resto del equipo de presentar un caso basándose en esas razones. Simplemente someterse ciegamente a la mayoría sin evaluar las cosas a través del estancamiento. Veo por las respuestas que para este caso concreto no hay una buena razón para eliminar el Is, pero eso no invalida haber pedido en primer lugar.
Vinko Vrsalovic
35
El libro del "Tío Bob" se centra en JAVA, no en C #. La mayoría de los paradigmas son iguales con C #, pero algunas convenciones de nomenclatura difieren (también mire los nombres de funciones lowerCamelCase en Java). Cuando escriba en C #, use las convenciones de nomenclatura de C #. Pero de todos modos, siga el punto principal de su capítulo sobre nomenclatura: ¡nombres legibles que comunican el significado del elemento!
Bernhard Hiller el

Respuestas:

191

Mientras que muchos, incluido el "Tío Bob", aconsejan no usar Icomo 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.

David Arno
fuente
Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
maple_shaft
9
No proporciona ninguna base para "En términos generales, debe evitarse". En Java, debería ser evitado. En C # debe ser aceptado. Otros idiomas siguen sus propias convenciones.
Básico
44
El principio "en términos generales" es simple: un cliente debe tener el derecho de no saber si se trata de una interfaz o una implementación. Ambos idiomas entendieron esto mal. C # se equivocó con la convención de nomenclatura. Java tiene el código de bytes incorrecto. Si cambio una implementación a una interfaz con el mismo nombre en Java, tengo que volver a compilar todos los clientes aunque el nombre y los métodos no hayan cambiado. No es un "elige tu veneno". Lo hemos estado haciendo mal. Aprenda de esto si está creando un nuevo idioma.
candied_orange
39

La interfaz es el concepto lógico importante, por lo tanto, la interfaz debe llevar el nombre genérico. Entonces, 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 de alguna manera 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 Something es una clase ahora, y luego descubre que tiene que introducir una interfaz, esa interfaz debe tener el mismo nombre, porque debe ser transparente para los clientes si el tipo es una clase concreta, una clase abstracta o una interfaz . Algo rompe eso.
Wallenborn
fuente
59
¿Por qué no seguirías el estándar bien establecido que proporciona C #? ¿Qué beneficio ofrece esto que excede el costo de confundir e irritar a cada programador de C # que lo sigue?
Robert Harvey
66
@wallenborn Está bien, pero de manera realista, a menudo solo tienes una única implementación legítima de una interfaz, porque es común usar interfaces para la simulación en pruebas unitarias. No quiero poner Defaulto Realo Implen muchas de mis nombres de clase
Ben Aaronson
44
Estoy de acuerdo con usted, pero si viaja por este camino, tendrá que luchar contra cada linter que se haya hecho para C #.
RubberDuck
3
No veo ningún problema con tener una clase instanciable con el mismo nombre que una interfaz si la gran mayoría de las instancias que implementan la interfaz serán esa clase. Por ejemplo, una gran cantidad de código necesita una implementación directa de propósito general IList<T>y realmente no le importa cómo se almacena internamente; ser capaz de simplemente cortar Iy 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 ordinaria List<T>debería usar ArrayList<T>.
supercat
2
@ArtB Un par de razones. Uno, no hay lenguaje en el que la convención sea un buen marcador corto en la clase 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 : Foopero SqlStore : Store. Ese es un problema que tengo Impl, que es esencialmente solo un marcador más feo. Tal vez si hubiera un lenguaje con la convención de agregar siempre un C(o lo que sea), eso podría ser mejor queI
Ben Aaronson el
27

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...

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

... es preferible a esto ...

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

OMI

Robbie Dee
fuente
77
Vale la pena señalar que Java evita cuidadosamente este problema mediante el uso de diferentes palabras clave para la herencia de clases y la implementación de la interfaz, es decir inheritsvs implements.
Konrad Rudolph el
66
@KonradRudolph quieres decir extends.
Deja de dañar a Monica el
2
@Andy El valor agregado es que podemos evitar la ruina de la notación húngara y, al mismo tiempo, preservar toda la claridad promocionada en esta respuesta.
Konrad Rudolph
2
Realmente no debería haber ninguna confusión; Si hereda una clase, DEBE estar primero en la lista, antes de la lista de interfaces implementadas. Estoy bastante seguro de que el compilador lo aplica.
Andy
1
@Andy ... si heredas una clase, DEBE ser la primera en la lista. Eso es genial, pero sin el prefijo no sabrías si estás tratando con un par de interfaces o una clase base y una interfaz ...
Robbie Dee
15

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 Iprefijo, 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 Icon ese nombre, al cliente no le importa. Si no hay I, al cliente no le importa. Con lo que está hablando podría ser concreto. Puede que no. Como no lo llama newde ninguna manera, no hay 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 decirle al cliente. Si no hay Itradició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.

naranja confitada
fuente
66
La consistencia es clave, pero en un lenguaje estáticamente tipado y con herramientas de refactorización modernas es muy fácil y seguro cambiar los nombres. Entonces, si el primer desarrollador hizo una mala elección al nombrar, no tiene que vivir con eso para siempre. Pero puede cambiar el nombre en su propia base de código, pero no puede cambiarlo en el marco o en las bibliotecas de terceros. Dado que el prefijo I (nos guste o no) es una convención establecida en todo el mundo .net, es mejor seguir la convención que no seguirla.
JacquesB
Si está usando C #, verá esos Is. 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.
Sebastian Redl
8

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 como Stream(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 el Basesufijo 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 y Enumerablecomo 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 Iprefijo 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:

IBananaEs la noción abstracta de plátano. Si puede haber una clase de implementación que no tenga un nombre mejor que el Banana, la abstracción no tiene ningún sentido, y debe abandonar la interfaz y simplemente usar una clase. Si hay un nombre mejor (digamos, LongBananao AppleBanana), no hay razón para no usarlo Bananacomo nombre de la interfaz. Por lo tanto, usar el Iprefijo 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 el Iprefijo de un tipo sería en un constructor, un ruido bastante inútil.

Si aplica esto a su IParserinterfaz 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 ejemplo JsonParser, 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 el Iprefijo, guárdelo. Solo toma una nota mental cada vez que veas un código class 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.

Luaan
fuente
3
Nitpick: LSP no dice que cada tipo debe ser no final. Simplemente dice que para los tipos que no son finales, los consumidores deben poder usar las subclases de manera transparente. - Y, por supuesto, Java también tiene tipos finales.
Konrad Rudolph el
@KonradRudolph Agregué una nota al pie sobre esto; gracias por los comentarios :)
Luaan
4

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

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 :)

utnapistim
fuente
2

Como mencionan las otras respuestas, el prefijo de los nombres de su interfaz Ies 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, IParserpor lo tanto, para la interfaz y Parserla 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 Ideben eliminarse y las interfaces deben tener los nombres más simples Parser, por ejemplo, para su interfaz de analizador. Pero en lugar de un nombre basado en roles como ParserDefault, la clase debería tener un nombre que describa cómo se implementa el analizador :

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
}

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

TheHansinator
fuente
55
También se prefieren las interfaces en C #. ¿Quién te dijo que prefería usar tipos concretos en dotnet?
RubberDuck
@RubberDuck Está oculto en algunos de los supuestos que el lenguaje te impone. Por ejemplo, el hecho de que los miembros son sealedpor defecto, y debe hacerlos explícitamente virtual. 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 con I), 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.
Luaan
1
Los miembros están sellados de forma predeterminada para que seamos explícitos sobre lo que los herederos pueden anular en @Luaan. Es cierto que a veces es un PITA, pero no tiene nada que ver con la preferencia por tipos concretos. Virtual ciertamente no es un caso especial en C #. Parece que tuvo la desgracia de trabajar en una base de código C # escrita por aficionados. Lamento que haya sido tu experiencia con el idioma. Es mejor recordar que el desarrollador no es el idioma y viceversa.
RubberDuck
@RubberDuck Hah, nunca dije que no me gustara. Lo prefiero mucho, porque la mayoría de las personas no entienden la indirección . Incluso entre programadores. Sellado por defecto es mejor en mi opinión. Y cuando comencé con C #, todos eran aficionados, incluido el equipo .NET :) En "OOP real", se supone que todo es virtual: cada tipo debe ser reemplazable por un tipo derivado, que debe ser capaz de anular cualquier comportamiento . Pero "OOP real" fue diseñado para especialistas, no para personas que cambiaron de reemplazar las bombillas a la programación porque es menos trabajo por más dinero :)
Luaan
No ... No. Así no es como funciona la "POO real". En Java, debería tomarse el tiempo para sellar métodos que no deberían anularse, no dejarlo como el Salvaje Oeste, donde cualquiera puede sobrepasar todo. LSP no significa que pueda anular lo que quiera. LSP significa que puede reemplazar de forma segura una clase con otra. Smh
RubberDuck
-8

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

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


namespace myapp.Parsers
{
    public class Parser : myapp.Interfaces.Parser {...
}
Ewan
fuente
11
¿nuevo? ¿antiguo? Espera, pensé que la programación tenía más que ver con la racionalidad que con la exageración. ¿O fue en los viejos tiempos?
3
no, todo se trata de inventar convenciones y bloguear sobre cómo 'mejoran la legibilidad'
Ewan
8
Creo que estás pensando en la notación húngara, en la que de hecho prefijaste nombres con tipos. Pero no había tiempo en el que escribirías algo como intCountero boolFlag. Esos son los "tipos" incorrectos. En su lugar, escribiría pszName(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.
Cody Gray el
2
Todavía hay mucho valor en esto. Un compilador no va a detectar el error cuando se agrega xControla cchName. Ambos son de tipo int, 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, el Iprefijo 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).
Cody Gray
44
@CodyGray Joel Spolsky escribió un buen artículo, Cómo hacer que el código incorrecto parezca incorrecto sobre la notación húngara y las formas en que se ha entendido (mal). Él tiene una opinión similar: el prefijo debe ser un tipo de nivel superior, no el tipo de almacenamiento de datos sin procesar. Él usa la palabra "amable", explicando: "Estoy usando la palabra amable a propósito, porque Simonyi usó erróneamente el tipo de palabra en su trabajo, y generaciones de programadores entendieron mal lo que quería decir".
Joshua Taylor el