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

67

Il capitolo di Uncle Bob sui nomi in Pulisci codice ti consiglia di evitare codifiche nei nomi, principalmente per quanto riguarda la notazione ungherese. Inoltre, menziona specificamente la rimozione del prefisso I dalle interfacce, ma non mostra esempi di questo.

Supponiamo che:

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

Quindi, ad esempio, come dovrebbero essere nominati questi due? Parser e ConcreteParser ? Parser e 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 questo?

    
posta Vinko Vrsalovic 23.08.2016 - 11:01
fonte

8 risposte

191

Mentre molti, incluso "Uncle Bob", consigliano di non usare I come prefisso per le interfacce, è una tradizione consolidata con C #. In termini generali, dovrebbe essere evitato. Ma se stai scrivendo C #, dovresti davvero seguire le convenzioni di quella lingua e usarla. Non farlo causerà un'enorme confusione con chiunque abbia familiarità con C # che cerca di leggere il tuo codice.

    
risposta data 23.08.2016 - 11:48
fonte
39

L'interfaccia è il concetto logico importante, quindi l'interfaccia dovrebbe contenere 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 diverse isues:

  1. Qualcosa è solo un'implementazione di ISomething, ma è quella con il nome generico, come se fosse in qualche modo speciale.
  2. MockSomething sembra derivare da Something, ma 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 nominata la stessa, perché dovrebbe essere trasparente per i client se il tipo è una classe concreta, una classe astratta o un'interfaccia . ISomething rompe quello.
risposta data 23.08.2016 - 12:30
fonte
27

Questo non è solo sulle convenzioni di denominazione. C # non supporta l'ereditarietà multipla, quindi questo uso legacy della notazione ungherese ha un piccolo vantaggio, seppur utile, in cui si eredita da una classe base e si implementano una o più interfacce. Quindi questo ...

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

... è preferibile a questo ...

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

IMO

    
risposta data 23.08.2016 - 16:55
fonte
15

No no no.

Le convenzioni di denominazione non sono una funzione del fandom di tuo 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 imposta lo standard.

Solo così puoi decidere. Dopodiché, sii coerente. Se qualcuno inizia a comportarsi in modo incoerente, porta via la pistola nerf e falli aggiornare la documentazione fino a quando non si pentono.

Quando leggo un nuovo codice base se non vedo mai un prefisso I 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 di impostare questo precedente per il tuo codice base ti invito 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 una lista di cose che posso chiamare contro quel nome. Quelli non possono cambiare a meno che non lo dica. Possiedo quella parte. Quello con cui sto parlando, l'interfaccia o meno, non è il mio dipartimento.

Se si introduce un I in questo nome, al cliente non interessa. Se non c'è I , al cliente non interessa. Quello a cui sta parlando potrebbe essere concreto. Potrebbe non farlo. Poiché non chiama new su di esso in alcun modo, non fa alcuna differenza. Come cliente, non lo so. Non voglio saperlo.

Ora come scimmia del 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'è una tradizione di I , potrei anche non rinominarla. Quale potrebbe essere un problema perché ora dobbiamo ricompilare il client perché anche se la fonte non si preoccupa del binario. Qualcosa di facile da perdere se non hai mai cambiato il nome.

Forse non è un problema per te. Forse no. Se lo è, è una buona ragione per preoccuparsene. Ma per amore dei giocattoli da scrivania non pretendiamo che dovremmo farlo ciecamente perché è il modo in cui è fatto in qualche modo. O hai una ragione dannatamente buona o non importa.

Non si tratta di vivere per sempre con lui. Non mi sta dando delle cagate sulla base del codice non conforme alla tua particolare mentalità a meno che tu non sia disposto a risolvere il "problema" ovunque. A meno che tu non abbia una buona ragione, non dovrei prendere la via della minima resistenza all'uniformità, non venire a me predicando la conformità. Ho cose migliori da fare.

    
risposta data 24.08.2016 - 03:37
fonte
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 vanno con questo influenzano la linea guida - in Java, ogni tipo pubblico dovrebbe essere considerato non-finale, in accordo con il Principio di sostituzione di Liskov [1]. Se hai solo un'implementazione, chiamerai la classe Parser ; se trovi che hai bisogno di più implementazioni, cambi semplicemente la classe in un'interfaccia con lo stesso nome e rinomina l'implementazione concreta in qualcosa di descrittivo.

In C #, l'ipotesi principale è che quando si ottiene una classe (il nome non inizia con I ), quella è la classe che si desidera. Intendiamoci, questo non è affatto accurato al 100% - un tipico contro-esempio potrebbe essere classi come Stream (che avrebbe dovuto essere un'interfaccia, o un paio di interfacce), e ognuno ha le proprie linee guida e sfondi da altre lingue . Esistono anche altre eccezioni come il suffisso Base , largamente usato, per denotare una classe astratta, proprio come con un'interfaccia, sapere si suppone che il tipo sia polimorfico.

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

In definitiva, il prefisso I è ampiamente usato nel mondo C # e, per estensione, nel mondo .NET (dato che gran parte del codice .NET è scritto in C #, ha senso seguire le linee guida C # per la maggior parte di le interfacce pubbliche). Questo significa che quasi certamente lavorerai con le librerie e il codice che segue questa notazione, e ha senso adottare la tradizione per evitare inutili confusioni - non è come omettere il prefisso renderà il tuo codice migliore:)

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

IBanana è la nozione astratta di banana. Se può esistere una classe di implementazione che non avrebbe un nome migliore di Banana , l'astrazione è completamente priva di significato e dovresti rilasciare l'interfaccia e usare solo una classe. Se c'è un nome migliore (ad esempio, LongBanana o AppleBanana ), non c'è motivo per non utilizzare Banana come nome dell'interfaccia. Pertanto, l'uso del prefisso I significa che si dispone di un'astrazione inutile, che rende il codice più difficile da comprendere senza alcun beneficio. E dal momento che l'OOP rigoroso avrà sempre il codice contro le interfacce, l'unico posto dove non vedresti il prefisso I su un tipo sarebbe su un costruttore - un rumore abbastanza inutile.

Se lo applichi all'interfaccia IParser di esempio, puoi vedere chiaramente che l'astrazione è interamente nel territorio "privo di significato". O c'è qualcosa di specifico su un'implementazione concreta di un parser (ad esempio JsonParser , XmlParser , ...), oppure dovresti semplicemente usare una classe. Non esiste una cosa come "implementazione di default" (sebbene in alcuni ambienti, questo ha davvero senso - in particolare, COM), o c'è un'implementazione specifica, oppure vuoi una classe astratta o metodi di estensione per i "valori predefiniti". Tuttavia, in C #, a meno che il tuo codebase non stia già omettendo I -prefix, tienilo. Basta prendere nota mentalmente ogni volta che vedi codice come class Something: ISomething - significa che qualcuno non è molto bravo a seguire YAGNI e costruire astrazioni ragionevoli.

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

    
risposta data 24.08.2016 - 11:17
fonte
4

So, for example, what should these two be named? Parser and ConcreteParser? Parser and ParserImplementation?

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

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

In altre parole, la convenzione offre una facile soluzione alla difficoltà di denominare le cose e l'easy out è in modo non corretto .

Nel mondo reale, i client del tuo codice (che potrebbero essere solo "tu dal futuro") devono essere in grado di leggerlo in seguito e di familiarizzarlo con il più rapidamente (o con il minimo sforzo possibile) ( dai ai tuoi clienti il codice che si aspettano di ottenere).

La convenzione di ISomething, introduce nomi cruft / cattivi. Ciò detto, il cruft non è reale cruft *), a meno che le convenzioni e le pratiche utilizzate nella scrittura del codice non siano più attuali; se la convenzione dice "usa ISomething", allora è meglio usarlo, per conformarsi (e lavorare meglio) con altri sviluppatori.

*) Posso farla franca dicendo che perché "cruft" non è un concetto esatto in ogni caso:)

    
risposta data 24.08.2016 - 12:02
fonte
2

Come menzionano le altre risposte, il prefisso dei nomi delle interfacce con I fa 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, IParser per l'interfaccia e Parser per l'implementazione predefinita .

Ma nel caso di Java e linguaggi simili, dove le interfacce sono preferite al posto delle classi concrete, lo zio Bob ha ragione nel fatto che il I deve essere rimosso e le interfacce dovrebbero avere i nomi più semplici - Parser per l'interfaccia del parser , per esempio. Ma invece di un nome basato sul ruolo come ParserDefault , la classe dovrebbe avere un nome che descrive come il parser è implementato :

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 è, ad esempio, il modo in cui Set<T> , HashSet<T> e TreeSet<T> sono nominati.

    
risposta data 23.08.2016 - 19:19
fonte
-8

Hai ragione nel senso che questa convenzione I = interface rompe le (nuove) regole

Ai vecchi tempi si faceva il prefisso di tutto con il suo tipo intCounter boolFlag ecc.

Se vuoi dare un nome alle cose senza il prefisso, ma anche evitare 'ConcreteParser' potresti usare namespace

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


namespace myapp.Parsers
{
    public class Parser : myapp.Interfaces.Parser {...
}
    
risposta data 23.08.2016 - 11:11
fonte

Leggi altre domande sui tag