Confundido acerca de las pautas de codificación de interfaz y clase para TypeScript

81

Leí las pautas de codificación de TypeScript

Y encontré esta declaración bastante desconcertante:

No use "I" como prefijo para los nombres de interfaz

Es decir, algo como esto no tendría mucho sentido sin el prefijo "I".

class Engine implements IEngine

¿Me estoy perdiendo algo obvio?

Otra cosa que no entendí del todo fue esto:

Clases

Por coherencia, no utilice clases en la canalización del compilador principal. En su lugar, utilice cierres de funciones.

¿Eso indica que no debería usar clases en absoluto?

Espero que alguien pueda aclararlo por mí :)

Snæbjørn
fuente
13
Para aclarar, esta es la documentación sobre el estilo del código para TypeScript, y no una guía de estilo sobre cómo implementar su proyecto. Si usar el Iprefijo tiene sentido para usted y su equipo, ÚSELO. Si no, tal vez el estilo Java de SomeThing(interfaz) con SomeThingImpl(implementación) entonces por todos los medios úselo.
Brocco
2
La belleza de las pautas de codificación es que son pautas , por lo que puede elegir implementarlas o no. Personalmente uso Ipara denotar una interfaz porque es a lo que estoy acostumbrado y tiene sentido para las personas de mi equipo.
Jon Preece
Sí, son solo pautas. Pero la mayoría de las veces hay una buena razón detrás de la directriz. Así que ceñirte a lo que estás acostumbrado no te desarrolla como programador :)
Snæbjørn
3
Cuando mi equipo comenzó a usar TS, también usamos el prefijo I para las interfaces. Influencia de C #, supongo. Luego leí las pautas del equipo de TypeScript sobre no usar el prefijo I. Me tomó varias semanas darme cuenta. Estaba buscando ansiosamente información sobre por qué tienen tal regla. Después de un tiempo me di cuenta: el prefijo I para interfaces es un defecto (al menos en TypeScript), causa más problemas que beneficios.
Stanislav Berkov
En VSCode, si escribe let engine = new E..., solo se sugerirán automáticamente las clases. Las interfaces no lo serán. No me había dado cuenta de eso, es una de las razones por las que estaba prefijando 'I'
Drenai

Respuestas:

163

Cuando un equipo / empresa envía un marco / compilador / conjunto de herramientas, ya tiene algo de experiencia, un conjunto de mejores prácticas. Lo comparten como pautas. Las pautas son recomendaciones . Si no le gusta ninguno, puede ignorarlo. El compilador aún compilará su código. Aunque cuando en Roma ...

Esta es mi visión por la que el equipo de TypeScript recomienda no Iprefijar interfaces.

Razón # 1 Los tiempos de la notación húngara han pasado

El argumento principal de los Ipartidarios de -prefix-for-interface es que el prefijo es útil para asimilar (mirar) inmediatamente si el tipo es una interfaz. La declaración de que el prefijo es útil para asimilar inmediatamente (mirar a escondidas) es una apelación a la notación húngara . Iprefijo para el nombre de la interfaz, Cpara la clase, Apara la clase abstracta, spara la variable de cadena, cpara la variable constante, ipara la variable entera. Estoy de acuerdo en que dicha decoración de nombre puede proporcionarle información de tipo sin pasar el mouse sobre el identificador o navegar hasta la definición de tipo a través de una tecla de acceso rápido. Este pequeño beneficio se ve compensado por las desventajas de la notación húngara y otras razones que se mencionan a continuación. La notación húngara no se utiliza en marcos contemporáneos. C # tieneIprefijo (y este es el único prefijo en C #) para interfaces debido a razones históricas (COM). En retrospectiva, uno de los arquitectos de .NET (Brad Abrams) piensa que hubiera sido mejor no usar el Iprefijo . TypeScript es COM-legacy-free, por lo que no tiene la Iregla -prefix-for-interface.

Razón # 2: el Iprefijo viola el principio de encapsulación

Supongamos que obtiene una caja negra. Obtienes algún tipo de referencia que te permite interactuar con ese cuadro. No debería importarle si es una interfaz o una clase. Solo usa su parte de interfaz. Exigir saber qué es (interfaz, implementación específica o clase abstracta) es una violación de la encapsulación.

Ejemplo: supongamos que necesita arreglar API Design Myth: Interface as Contract en su código, por ejemplo, elimine la ICarinterfaz y use Carla clase base en su lugar. Entonces debe realizar dicho reemplazo en todos los consumidores. I-prefix conduce a una dependencia implícita de los consumidores en los detalles de implementación de la caja negra.

Razón # 3 Protección de nombres incorrectos

Los desarrolladores son perezosos para pensar correctamente en los nombres. Nombrar es una de las dos cosas difíciles de la informática . Cuando un desarrollador necesita extraer una interfaz, es fácil agregar la letra Ial nombre de la clase y obtendrá un nombre de interfaz. No permitir el Iprefijo para las interfaces obliga a los desarrolladores a esforzarse en elegir los nombres adecuados para las interfaces. Los nombres elegidos deben ser diferentes no solo en el prefijo, sino también enfatizar la diferencia de intención.

Abstracción caso: usted debe no no definir una ICarinterfaz y un asociado Carde clase. Cares una abstracción y debe ser la utilizada para el contrato. Las implementaciones deben tener nombres descriptivos y distintivos, por ejemplo SportsCar, SuvCar, HollowCar.

Buen ejemplo: WpfeServerAutosuggestManager implements AutosuggestManager, FileBasedAutosuggestManager implements AutosuggestManager.

Mal ejemplo: AutosuggestManager implements IAutosuggestManager.

Razón # 4 Los nombres elegidos adecuadamente lo vacunan contra el Mito del Diseño de API: Interfaz como contrato .

En mi práctica, conocí a mucha gente que sin pensarlo duplicaba parte de la interfaz de una clase en una interfaz separada que tenía un Car implements ICaresquema de nombres. Duplicar parte de la interfaz de una clase en un tipo de interfaz separada no la convierte mágicamente en abstracción. Aún obtendrá una implementación concreta pero con una parte de interfaz duplicada. Si su abstracción no es tan buena, duplicar la parte de la interfaz no la mejorará de todos modos. Extraer abstracción es un trabajo duro.

NOTA: En TS no necesita una interfaz separada para burlarse de las clases o sobrecargar la funcionalidad. En lugar de crear una interfaz separada que describa a los miembros públicos de una clase, puede usar tipos de utilidad de TypeScript . Por ejemplo, Required<T>construye un tipo que consta de todos los miembros públicos del tipo T.

export class SecurityPrincipalStub implements Required<SecurityPrincipal> {
  public isFeatureEnabled(entitlement: Entitlement): boolean {
      return true;
  }
  public isWidgetEnabled(kind: string): boolean {
      return true;
  }

  public areAdminToolsEnabled(): boolean {
      return true;
  }
}

Si desea construir un tipo que excluya a algunos miembros públicos, puede usar la combinación de Omitir y Excluir .

Stanislav Berkov
fuente
3
Me gusta su punto de nombrar clases concretas más específicas que los nombres de interfaz, pero creo que el Iprefijo es útil para asimilar inmediatamente si el tipo tiene una implementación o no. La principal ventaja de utilizar interfaces es no acoplar los detalles de implementación del código externo a una implementación específica.
cchamberlain
1
@demisx, si se siente incómodo, no debería hacerlo. Responde la pregunta: ¿realmente los necesitas? ¿Tienen algún sentido o los usas solo por un falso sentido de hacer la ingeniería correcta?
Stanislav Berkov
2
Si bien sus puntos suenan bien en teoría, la realidad es que el prefijo "I" a menudo se cambia por el sufijo "Impl" en equipos lo suficientemente grandes
jameslk
3
@jameslk Sé nombrar cosas sin Iy Imples difícil ("Solo hay dos cosas difíciles en Ciencias de la Computación: invalidación de caché y nombrar cosas" - Phil Karlton) pero es factible.
Stanislav Berkov
5
Estoy de acuerdo en que la eliminación de I generalmente se reemplaza agregando un prefijo o sufijo a las implementaciones: DefaultFooService o FoodServiceImpl. Con prefijo, obtiene asimilación y no hay problemas de nombres superpuestos entre contratos e implementaciones / abstracciones. También el punto sobre la repetición es válido, pero el punto de las interfaces es tener contratos y poder reemplazar la implementación con algo más. Por lo general, esto es absolutamente crucial en las pruebas unitarias ... sin mencionar los beneficios de las abstracciones y la inmutabilidad, etc. Estoy a favor de las interfaces que prefijos.
chris
45

Aclaración sobre el enlace al que hace referencia:

Esta es la documentación sobre el estilo del código para TypeScript, y no una guía de estilo sobre cómo implementar su proyecto.

Si usar el Iprefijo tiene sentido para usted y su equipo, utilícelo (yo lo hago).

Si no, tal vez el estilo Java de SomeThing(interfaz) con SomeThingImpl(implementación) entonces por todos los medios úselo.

Brocco
fuente
19
Solo para aclarar. El manual de TypeScript todavía dice: In general, do not prefix interfaces with I (e.g. IColor). Because the concept of an interface in TypeScript is much more broad than in C# or Java, the IFoo naming convention is not broadly useful.No es estricto, pero parece una recomendación sólida.
Guria
3
De acuerdo. Suena como una guía de código de Microsoft para todos los proyectos basados ​​en TypeScript, no solo el TypeScript en sí. Encontramos esta directriz muy confusa y aún prefijamos todas las interfaces con "I".
demisx
3
Hay un escenario que creo que tiene sentido mantener I. En un proyecto de React tenemos Cardque es una interfaz y un componente llamado Card. En los accesorios, necesito una matriz de Card, pero el tipo no el componente. Tengo que elegir nombrar ICardo CardComponent...
BrunoLM
3
SomeThingImpl implements SomeThinges tan bueno como SomeThing implements ISomeThing. Si SomeThinges una abstracción, entonces la denominación debería serlo Concrete[Some]Thing implements SomeThing.
Stanislav Berkov
Como dijo @Guria, creo que el manual de TypeScript lo dice todo, solo tenemos que prestar atención a los ejemplos y sabremos cómo usarlo bien.
giovannipds
0

Encuentro que @ stanislav-berkov es una respuesta bastante buena a la pregunta del OP. Solo compartiría mis 2 centavos agregando que, al final, depende de su Equipo / Departamento / Compañía / Lo que sea llegar a un entendimiento común y establecer sus propias reglas / pautas a seguir.

Cumplir con los estándares y / o convenciones, siempre que sea posible y deseable, es una buena práctica y facilita la comprensión de las cosas. Por otro lado, me gusta pensar que todavía somos libres de elegir la forma en que escribimos nuestro código.

Pensando un poco en el lado emocional, la forma en que escribimos el código o nuestro estilo de codificación refleja nuestra personalidad y, en algunos casos, incluso nuestro estado de ánimo. Esto es lo que nos mantiene a los humanos y no solo a las máquinas de codificación siguiendo reglas. Creo que la codificación puede ser un oficio, no solo un proceso industrializado.

Carlos Gonçalves
fuente
-1

El tipo de interfaz es un detalle de implementación. Los detalles de implementación deben estar ocultos en API: s. Es por eso que debes evitarlo I.

Debe evitar tanto prefix y suffix . Ambos están mal:

  • ICar
  • CarInterface

Lo que debe hacer es hacer visible un nombre bonito en la API y tener los detalles de implementación ocultos en la implementación. Por eso propongo:

  • Car - Una interfaz que se expone en la API.
  • CarImpl - Una implementación de esa API, que está oculta al consumidor.
Tomas Bjerre
fuente
Actualmente también utilizo esta estructura que se estableció a partir de mi experiencia en SpringBoot. ¿Entonces esto se considera apropiado en TS? Parece haber muchas opiniones en la comunidad de TS sobre lo que es correcto sin una respuesta definitiva.
Impureza
2
Usar el Implsufijo tendrá el efecto secundario de reemplazar cualquier sabelotodo que lo esté regañando por el Iprefijo con el tío Bob.
Szczepan Hołyszewski