Problemi di denominazione: "ISomething" deve essere rinominato in "Something"? [chiuso]


68

Il capitolo di zio Bob sui nomi in Clean Code raccomanda di evitare la codifica nei nomi, principalmente per quanto riguarda la notazione ungherese. Inoltre menziona specificamente la rimozione del Iprefisso dalle interfacce, ma non mostra esempi di ciò.

Supponiamo che:

  • L'uso dell'interfaccia è principalmente quello di raggiungere la testabilità attraverso l'iniezione di dipendenza
  • In molti casi, ciò porta ad avere un'unica interfaccia con un singolo implementatore

Quindi, per esempio, come dovrebbero essere chiamati questi due? Parsere ConcreteParser? Parsere ParserImplementation?

public interface IParser {
    string Parse(string content);
    string Parse(FileInfo path);
}

public class Parser : IParser {
     // Implementations
}

O dovrei ignorare questo suggerimento in casi di implementazione singola come questa?


30
Ciò non lo rende affatto meno una religione. Devi cercare ragioni, non solo qualcuno X dice Y.
Mike Dunlavey,

35
@MikeDunlavey E questa è la ragione della domanda!
Vinko Vrsalovic,

14
Se esiste un corpo di codice esistente (che non hai intenzione di riscrivere), segui la convenzione che utilizza. Se si tratta di un nuovo codice, utilizzare qualunque sia la maggior parte delle persone che stanno per lavorarci su. Solo se nessuno dei due precedenti si applica, dovrebbe diventare una questione filosofica.
TripeHound,

9
La regola della maggioranza di @TripeHound non è sempre l'opzione migliore. Se ci sono ragioni per cui alcune cose sono migliori di altre, una sola persona può convincere il resto della squadra a presentare un caso sulla base di tali motivi. Sottomettersi ciecamente alla maggioranza senza valutare le cose porta alla stagnazione. Vedo dalle risposte che per questo caso concreto non c'è una buona ragione per rimuovere l'Is, ma ciò non invalida averlo chiesto in primo luogo.
Vinko Vrsalovic,

35
Il libro di "Uncle Bob's" si concentra su JAVA, non su C #. La maggior parte dei paradigmi sono gli stessi con C #, ma alcune convenzioni di denominazione differiscono (guarda anche i nomi delle funzioni lowerCamelCase in Java). Quando scrivi in ​​C #, usa le convenzioni di denominazione in C #. Ma comunque, segui il punto principale del suo capitolo sulla denominazione: nomi leggibili che comunicano il significato dell'elemento!
Bernhard Hiller,

Risposte:


191

Mentre molti, tra cui "Zio Bob", consigliano di non utilizzare Icome prefisso per le interfacce, farlo è una tradizione consolidata con C #. In termini generali, dovrebbe essere evitato. Ma se stai scrivendo C #, dovresti davvero seguire le convenzioni di quel linguaggio e usarlo. Non farlo causerà grande confusione con chiunque abbia familiarità con C # che tenta di leggere il codice.


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
maple_shaft

9
Non fornisci alcuna base per "In termini generali, dovrebbe essere evitato". In Java, dovrei essere evitato. In C # dovrebbe essere abbracciato. Altre lingue seguono le proprie convenzioni.
Base

4
Il principio "in termini generali" è semplice: un cliente dovrebbe avere il diritto di non sapere nemmeno se sta parlando con un'interfaccia o un'implementazione. Entrambe le lingue hanno sbagliato. C # ha sbagliato la convenzione di denominazione. Java ha sbagliato il bytecode. Se passo un'implementazione a un'interfaccia con lo stesso nome in Java, devo ricompilare tutti i client anche se il nome e i metodi non sono cambiati. Non è un "prendi il tuo veleno". L'abbiamo appena fatto male. Si prega di imparare da questo se si sta creando una nuova lingua.
candied_orange,

39

L'interfaccia è l'importante concetto logico, quindi l'interfaccia dovrebbe avere il nome generico. Quindi, preferirei

interface Something
class DefaultSomething : Something
class MockSomething : Something

di

interface ISomething
class Something : ISomething
class MockSomething : ISomething

Quest'ultimo ha diversi aspetti:

  1. Qualcosa è solo un'implementazione di ISomething, tuttavia è quella con il nome generico, come se fosse in qualche modo speciale.
  2. MockSomething sembra derivare da Something, tuttavia implementa ISomething. Forse dovrebbe essere chiamato MockISomething, ma non l'ho mai visto in natura.
  3. Il refactoring è più difficile. Se Something è una classe ora, e scopri solo in seguito che devi introdurre un'interfaccia, quell'interfaccia dovrebbe essere denominata la stessa, perché dovrebbe essere trasparente per i client se il tipo è una classe concreta, una classe astratta o un'interfaccia . Qualcosa lo rompe.

59
Perché non dovresti seguire lo standard consolidato che C # fornisce? Che vantaggio offre questo che supera il costo di confondere e irritare ogni programmatore C # che ti segue?
Robert Harvey,

6
@wallenborn Va bene, ma realisticamente spesso hai una sola legittima implementazione di un'interfaccia, perché è comune usare interfacce per la derisione nei test unitari. Non voglio mettere Defaulto Realo Implin tanti dei miei nomi di classe
Ben Aaronson

4
Sono d'accordo con te, ma se percorri questa strada dovrai combattere ogni linter mai realizzato per C #.
RubberDuck,

3
Non vedo alcun problema con avere una classe istantanea con lo stesso nome di un'interfaccia se la stragrande maggioranza delle istanze che implementano l'interfaccia sarà quella classe. Ad esempio, un sacco di codice ha bisogno di un'implementazione per scopi generici IList<T>e non gli interessa davvero come viene archiviato internamente; essere in grado di ignorare Ie avere un nome di classe utilizzabile sembra più bello che dover magicamente sapere (come in Java) che List<T>dovrebbe usare il codice che desidera un'implementazione ordinaria ArrayList<T>.
supercat

2
@ArtB Coppia di motivi. Primo, non esiste un linguaggio in cui la convention sia un bel marcatore breve della classe CFoo : Foo. Quindi questa opzione non è davvero disponibile. Due, ottieni una strana incoerenza perché alcune classi avrebbero il marcatore e altre no, a seconda che ci possano essere altre implementazioni della stessa interfaccia. CFoo : Fooma SqlStore : Store. Questo è un problema che ho Impl, che è essenzialmente solo un marcatore più brutto. Forse se ci fosse una lingua con la convenzione di aggiungere sempre un C(o qualsiasi altra cosa), potrebbe essere meglio diI
Ben Aaronson,

27

Non si tratta solo di convenzioni di denominazione. C # non supporta l'ereditarietà multipla, quindi questo uso legacy della notazione ungherese ha un piccolo, sebbene utile vantaggio, in cui erediti da una classe base e implementi una o più interfacce. Così questo...

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

... è preferibile a questo ...

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

IMO


7
Vale la pena notare che Java evita accuratamente questo problema utilizzando diverse parole chiave per la classe eredità e implementazione di interfaccia, cioè inheritsvs implements.
Konrad Rudolph,

6
@KonradRudolph intendi extends.
Smetti di fare del male a Monica il

2
@Andy Il valore aggiunto è che possiamo evitare l'innesto di Notazione ungherese, conservando tuttavia tutta la chiarezza propagandata in questa risposta.
Konrad Rudolph,

2
Non dovrebbe esserci alcuna confusione; se erediti una classe, DEVE essere la prima nell'elenco, prima dell'elenco delle interfacce implementate. Sono abbastanza sicuro che questo sia applicato dal compilatore.
Andy,

1
@Andy ... se erediti una classe, DEVE essere il primo nella lista È fantastico ma senza il prefisso non sapresti se avresti a che fare con un paio di interfacce o una classe base e un'interfaccia ...
Robbie Dee,

15

No no no

Le convenzioni di denominazione non sono una funzione del tuo fandom di zio Bob. Non sono una funzione della lingua, c # o altro.

Sono una funzione della tua base di codice. In misura molto minore del tuo negozio. In altre parole, il primo nella base di codice stabilisce lo standard.

Solo allora puoi decidere. Dopodiché sii coerente. Se qualcuno inizia a essere incoerente, porta via la pistola nerf e fai aggiornare la documentazione fino a quando non si pentono.

Quando leggo una nuova base di codice se non vedo mai un Iprefisso, sto bene, indipendentemente dalla lingua. Se ne vedo uno su ogni interfaccia, sto bene. Se a volte ne vedo uno, qualcuno pagherà.

Se ti trovi nel contesto rarefatto dell'impostazione di questo precedente per la tua base di codice, ti esorto a considerare questo:

Come classe cliente mi riservo il diritto di non fregarmene di cosa sto parlando. Tutto ciò di cui ho bisogno è un nome e un elenco di cose che posso chiamare contro quel nome. Quelli non possono cambiare se non lo dico io. PROPRIO quella parte. Quello di cui sto parlando, interfaccia o no, non è il mio dipartimento.

Se si intrufola Icon quel nome, al cliente non importa. In caso Icontrario, al cliente non importa. Ciò con cui sta parlando potrebbe essere concreto. Potrebbe no. Dal momento che non lo chiama newin entrambi i modi non fa alcuna differenza. Come cliente, non lo so. Non voglio sapere

Ora, come una scimmia di codice che guarda quel codice, anche in Java, questo potrebbe essere importante. Dopotutto, se devo cambiare un'implementazione in un'interfaccia, posso farlo senza dirlo al cliente. Se non c'è Itradizione non potrei nemmeno rinominarla. Il che potrebbe essere un problema perché ora dobbiamo ricompilare il client perché anche se al sorgente non importa il binario. Qualcosa di facile da perdere se non hai mai cambiato il nome.

Forse non è un problema per te. Forse no. Se lo è, questa è una buona ragione per preoccuparsene. Ma per amore dei giocattoli da scrivania non pretendere che dovremmo semplicemente farlo ciecamente perché è il modo in cui è fatto in qualche lingua. O hai una dannata buona ragione o non ti interessa.

Non si tratta di conviverci per sempre. Si tratta di non farmi fregare che la base di codice non sia conforme alla vostra particolare mentalità a meno che non siate pronti a risolvere il "problema" ovunque. A meno che tu non abbia una buona ragione per cui non dovrei prendere la strada della minima resistenza all'uniformità, non venire da me predicando la conformità. Ho cose migliori da fare.


6
La coerenza è la chiave, ma in un linguaggio tipicamente statico e con i moderni strumenti di refactoring è abbastanza facile e sicuro cambiare nome. Quindi, se il primo sviluppatore ha fatto una cattiva scelta nella denominazione, non devi conviverci per sempre. Ma puoi cambiare il nome nella tua base di codice, ma non puoi cambiarlo nel framework o nelle librerie di terze parti. Poiché l'I-prefisso (piaccia o no) è una convenzione stabilita in tutto il mondo .net, è meglio seguire la convenzione piuttosto che non seguirla.
Jacques B

Se stai usando C #, vedrai quelle Is. Se il tuo codice li utilizza, li vedrai ovunque. Se il tuo codice non li utilizza, li vedrai dal codice del framework, portando esattamente al mix che non desideri.
Sebastian Redl,

8

C'è una piccola differenza tra Java e C # che è rilevante qui. In Java, ogni membro è virtuale per impostazione predefinita. In C #, ogni membro è sigillato per impostazione predefinita, ad eccezione dei membri dell'interfaccia.

Le ipotesi che ne derivano influenzano le linee guida: in Java, ogni tipo pubblico dovrebbe essere considerato non definitivo, in conformità con il principio di sostituzione di Liskov [1]. Se hai una sola implementazione, nominerai la classe Parser; se trovi che hai bisogno di più implementazioni, cambierai la classe in un'interfaccia con lo stesso nome e rinominerai l'implementazione concreta in qualcosa di descrittivo.

In C #, il presupposto principale è che quando ottieni una classe (il nome non inizia con I), quella è la classe che desideri. Intendiamoci, questo non è assolutamente accurato al 100%: un tipico esempio di esempio sarebbe rappresentato da classi come Stream(che in realtà avrebbero dovuto essere un'interfaccia o un paio di interfacce) e ognuno ha le proprie linee guida e sfondi da altre lingue. Ci sono anche altre eccezioni come il Basesuffisso abbastanza ampiamente usato per indicare una classe astratta - proprio come con un'interfaccia, sai che il tipo dovrebbe essere polimorfico.

C'è anche una buona funzionalità di usabilità nel lasciare il nome senza I-prefisso per funzionalità che si riferisce a quell'interfaccia senza dover ricorrere a rendere l'interfaccia una classe astratta (che danneggerebbe a causa della mancanza di ereditarietà multipla di classe in C #). Questo è stato reso popolare da LINQ, che utilizza IEnumerable<T>come interfaccia e Enumerablecome repository di metodi che si applicano a tale interfaccia. Ciò non è necessario in Java, dove anche le interfacce possono contenere implementazioni di metodi.

In definitiva, il Iprefisso è ampiamente usato nel mondo C # e, per estensione, nel mondo .NET (poiché gran parte del codice .NET è scritto in C #, ha senso seguire le linee guida C # per la maggior parte delle interfacce pubbliche). Ciò significa che quasi sicuramente lavorerai con librerie e codice che seguono questa notazione, e ha senso adottare la tradizione per prevenire inutili confusioni - non è come omettere il prefisso renderà il tuo codice migliore :)

Suppongo che il ragionamento di zio Bob fosse qualcosa del genere:

IBananaè la nozione astratta di banana. Se può esserci una classe di implementazione che non avrebbe un nome migliore di Banana, l'astrazione è del tutto priva di significato, e dovresti abbandonare l'interfaccia e usare solo una classe. Se esiste un nome migliore (diciamo, LongBananao AppleBanana), non c'è motivo di non usarlo Bananacome nome dell'interfaccia. Pertanto, l'utilizzo del Iprefisso significa che hai un'astrazione inutile, che rende il codice più difficile da capire senza alcun vantaggio. E dal momento che OOP rigoroso ti farà sempre programmare contro le interfacce, l'unico posto in cui non vedresti il Iprefisso su un tipo sarebbe su un costruttore - rumore abbastanza inutile.

Se lo applichi all'interfaccia di esempio IParser, puoi vedere chiaramente che l'astrazione è interamente nel territorio "insignificante". O c'è qualcosa di specifico su un'implementazione concreta di un parser (ad es JsonParser. XmlParser, ...) o dovresti semplicemente usare una classe. Non esiste qualcosa come "implementazione predefinita" (anche se in alcuni ambienti, questo ha davvero senso, in particolare COM), o c'è un'implementazione specifica o si desidera una classe astratta o metodi di estensione per le "impostazioni predefinite". Tuttavia, in C #, a meno che il tuo codebase non ometta già il I-prefix, tienilo. Prendi solo una nota mentale ogni volta che vedi un codice simile class Something: ISomething- significa che qualcuno non è molto bravo a seguire YAGNI e costruire astrazioni ragionevoli.

[1] - Tecnicamente, questo non è specificamente menzionato nel documento di Liskov, ma è uno dei fondamenti del documento originale OOP e nella mia lettura di Liskov, non ha contestato questo. In un'interpretazione meno rigorosa (quella adottata dalla maggior parte delle lingue OOP), ciò significa che qualsiasi codice che utilizza un tipo pubblico destinato alla sostituzione (ovvero non definitivo / sigillato) deve funzionare con qualsiasi implementazione conforme di quel tipo.


3
Nitpick: LSP non dice che ogni tipo dovrebbe essere non definitivo. Dice solo che per i tipi che sono non-finale, i consumatori devono essere in grado di utilizzare in modo trasparente sottoclassi. - E ovviamente Java ha anche i tipi finali.
Konrad Rudolph,

@KonradRudolph Ho aggiunto una nota a piè di pagina in merito; grazie per il feedback :)
Luaan

4

Quindi, per esempio, come dovrebbero essere chiamati questi due? Parser e ConcreteParser? Parser e ParserImplementation?

In un mondo ideale, non dovresti aggiungere il prefisso al tuo nome con una scorciatoia ("I ...") ma lasciare semplicemente che il nome esprima un concetto.

Ho letto da qualche parte (e non posso procurarmelo) l'opinione che questa convenzione ISomething ti porti a definire un concetto "IDollar" quando hai bisogno di una classe "Dollar", ma la soluzione corretta sarebbe un concetto chiamato semplicemente "Valuta".

In altre parole, la convenzione fornisce una soluzione alla difficoltà di nominare le cose e la soluzione è leggermente sbagliata .


Nel mondo reale, i clienti del tuo codice (che potrebbe essere solo "tu dal futuro") devono essere in grado di leggerlo in un secondo momento e conoscerlo il più rapidamente (o con il minimo sforzo) possibile (dai ai clienti del tuo codice ciò che si aspettano di ottenere).

La convenzione ISomething introduce cruft / nomi cattivi. Detto questo, la cruft non è una vera cruft *), a meno che le convenzioni e le pratiche utilizzate nella scrittura del codice non siano più effettive; se la convenzione dice "usa ISomething", allora è meglio usarlo, per conformarsi (e lavorare meglio) con altri sviluppatori.


*) Posso cavarmela dicendo che questo perché "cruft" non è comunque un concetto esatto :)


2

Come menzionano le altre risposte, il prefisso dei nomi delle tue interfacce Ifa parte delle linee guida di codifica per .NET framework. Poiché le classi concrete sono più spesso interagite con le interfacce generiche in C # e VB.NET, sono di prima classe negli schemi di denominazione e quindi dovrebbero avere i nomi più semplici - quindi, IParserper l'interfaccia e Parserper l'implementazione predefinita.

Ma nel caso di Java e di linguaggi simili, dove si preferiscono le interfacce anziché le classi concrete, lo zio Bob ha ragione nel ritenere che Idovrebbero essere rimosse e le interfacce dovrebbero avere i nomi più semplici Parser, ad esempio per l'interfaccia del parser. Ma invece di un nome basato sul ruolo come ParserDefault, la classe dovrebbe avere un nome che descriva come viene implementato il parser :

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
}

Questo è, per esempio, come Set<T>, HashSet<T>e TreeSet<T>prendono il nome.


5
Le interfacce sono preferite anche in C #. Chi ti ha detto che era preferito usare tipi concreti in dotnet?
RubberDuck,

@RubberDuck È nascosto in alcune delle ipotesi che la lingua ti impone. Ad esempio, il fatto che i membri siano sealedpredefiniti e devi renderli esplicitamente virtual. Essere virtuali è il caso speciale in C #. Naturalmente, poiché l'approccio raccomandato alla scrittura del codice è cambiato nel corso degli anni, molte reliquie del passato dovevano rimanere per compatibilità :) Il punto chiave è che se ottieni un'interfaccia in C # (che inizia con I), sai che è interamente tipo astratto; se si ottiene una non interfaccia, il presupposto tipico è che questo è il tipo desiderato, LSP sia dannato.
Luaan,

1
I membri sono sigillati per impostazione predefinita in modo da essere espliciti su ciò che gli eredi possono superare su @Luaan. Certo, a volte è un PITA, ma non ha nulla a che fare con una preferenza per i tipi concreti. Il virtuale non è certamente un caso speciale in C #. Sembra che tu abbia avuto la sfortuna di lavorare in una base di codice C # scritta da dilettanti. Mi dispiace che sia stata la tua esperienza con la lingua. È meglio ricordare che lo sviluppatore non è la lingua e viceversa.
RubberDuck,

@RubberDuck Hah, non ho mai detto che non mi piaceva. Lo preferisco di gran lunga, perché la maggior parte delle persone non capisce il riferimento indiretto . Anche tra i programmatori. Sigillato di default è meglio secondo me. E quando ho iniziato con C #, tutti erano dilettanti, incluso il team .NET :) In "OOP reale", tutto dovrebbe essere virtuale - ogni tipo dovrebbe essere sostituibile con un tipo derivato, che dovrebbe essere in grado di ignorare qualsiasi comportamento . Ma il "vero OOP" è stato progettato per gli specialisti, non per le persone che sono passate dalla sostituzione delle lampadine alla programmazione perché è meno lavoro per più soldi :)
Luaan,

No ... No. Non è così che funziona il "vero OOP". In Java, dovresti prenderti il ​​tempo per sigillare metodi che non dovrebbero essere ignorati, non lasciandoli come il selvaggio West, dove chiunque può cavalcare tutto. LSP non significa che puoi ignorare tutto ciò che vuoi. LSP significa che puoi sostituire in sicurezza una classe con un'altra. Smh
RubberDuck

-8

Hai ragione nel dire che questa convenzione I = interface infrange le (nuove) regole

Ai vecchi tempi si anteponeva tutto con il suo tipo intCounter boolFlag ecc.

Se vuoi nominare le cose senza il prefisso, ma anche evitare "ConcreteParser" puoi usare gli spazi dei nomi

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


namespace myapp.Parsers
{
    public class Parser : myapp.Interfaces.Parser {...
}

11
nuovo? vecchio? Aspetta, pensavo che la programmazione riguardasse più la razionalità che l'hype. O era ai vecchi tempi?

3
non si tratta solo di inventare convenzioni e blog su come "migliorano la leggibilità"
Ewan

8
Credo che tu stia pensando alla notazione ungherese, in cui in effetti hai prefisso i nomi con i tipi. Ma non c'era tempo in cui scrivevi qualcosa di simile intCountero boolFlag. Questi sono i "tipi" sbagliati. Invece, dovresti scrivere pszName(puntatore a una stringa terminata con NUL che contiene un nome), cchName(conteggio dei caratteri nella stringa "nome"), xControl(coordinata x del controllo), fVisible(flag che indica qualcosa è visibile), pFile(puntatore a un file), hWindow(gestire una finestra) e così via.
Cody Grey,

2
C'è ancora molto valore in questo. Un compilatore non ha intenzione di rilevare l'errore quando si aggiunge xControla cchName. Sono entrambi di tipo int, ma hanno una semantica molto diversa. I prefissi rendono evidente questa semantica a un essere umano. Un puntatore a una stringa terminata con NUL assomiglia esattamente a un puntatore a una stringa in stile Pascal per un compilatore. Allo stesso modo, il Iprefisso per le interfacce è emerso nel linguaggio C ++ in cui le interfacce sono in realtà solo classi (e in effetti per C, dove non esiste una classe o un'interfaccia a livello di linguaggio, è solo una convenzione).
Cody Grey,

4
@CodyGray Joel Spolsky ha scritto un bell'articolo, facendo sembrare sbagliato il codice errato sulla notazione ungherese e sui modi in cui è stato (mis) capito. Ha un'opinione simile: il prefisso dovrebbe essere un tipo di livello superiore, non il tipo di archiviazione dei dati non elaborati. Usa la parola "gentile", spiegando "Sto usando la parola gentile apposta, lì, perché Simonyi ha erroneamente usato la parola tipo nel suo documento e generazioni di programmatori hanno frainteso ciò che intendeva dire".
Joshua Taylor,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.