Cosa dovrebbe avere la precedenza: YAGNI o Good Design?


76

A che punto YAGNI dovrebbe avere la precedenza sulle buone pratiche di codifica e viceversa? Sto lavorando a un progetto sul posto di lavoro e voglio introdurre lentamente buoni standard di codice ai miei colleghi (al momento non ce ne sono e tutto è solo un po 'hackerato insieme senza rima o motivo), ma dopo aver creato una serie di classi (noi non fare TDD, o purtroppo nessun tipo di test unitario) Ho fatto un passo indietro e ho pensato che stesse violando YAGNI perché so con certezza che non abbiamo bisogno di estendere alcune di queste classi.

Ecco un esempio concreto di ciò che intendo: ho un livello di accesso ai dati che avvolge una serie di procedure memorizzate, che utilizza un modello rudimentale in stile repository con funzioni CRUD di base. Dato che ci sono una manciata di metodi di cui hanno bisogno tutte le mie classi di repository, ho creato un'interfaccia generica per i miei repository, chiamata IRepository. Tuttavia, ho quindi creato un'interfaccia "marker" (ovvero un'interfaccia che non aggiunge alcuna nuova funzionalità) per ogni tipo di repository (ad esempio ICustomerRepository) e la classe concreta lo implementa. Ho fatto la stessa cosa con un'implementazione Factory per creare gli oggetti business da DataReaders / DataSet restituiti dalla Stored Procedure; la firma della mia classe di repository tende ad assomigliare a questa:

public class CustomerRepository : ICustomerRepository
{
    ICustomerFactory factory = null;

    public CustomerRepository() : this(new CustomerFactory() { }

    public CustomerRepository(ICustomerFactory factory) {
        this.factory = factory;
    }      

    public Customer Find(int customerID)
    {
        // data access stuff here
        return factory.Build(ds.Tables[0].Rows[0]);
    }
}

La mia preoccupazione qui è che sto violando YAGNI perché so con il 99% di certezza che non ci sarà mai un motivo per dare qualcosa di diverso da un concreto CustomerFactorya questo repository; dal momento che non abbiamo test unitari, non ho bisogno di MockCustomerFactorycose simili o simili e avere così tante interfacce potrebbe confondere i miei colleghi. D'altra parte, l'utilizzo di un'implementazione concreta della fabbrica sembra un odore di design.

Esiste un buon modo per arrivare a un compromesso tra la corretta progettazione del software e la non architettura eccessiva della soluzione? Mi chiedo se ho bisogno di avere tutte le "singole interfacce di implementazione" o se potrei sacrificare un po 'di buona progettazione e avere solo, per esempio, l'interfaccia di base e quindi il singolo concreto, e non preoccuparmi di programmare per interfaccia se l'implementazione è quella che verrà mai utilizzata.


17
Dici "dal momento che non abbiamo test unitari non ho bisogno di MockX" che porta naturalmente a "Non ho bisogno di IX, ho solo bisogno di X". Lo capisco, il fatto che non hai test unitari evidenzia il fatto che hai bisogno di IX e MockX, perché queste cose ti aiuteranno a fare test unitari. Non accettare la realtà di non avere test, trattalo come un problema temporaneo che verrà risolto nel corso (probabilmente per un lungo, lungo) tempo.
Anthony Pegram,

10
Anche se è banale cercarlo su Google, qualcuno dovrebbe dire che YAGNI sta per "Non ne
avrai

1
Penserei che se stessi scrivendo nuove lezioni come questa, vorresti aggiungere alcuni test unitari. Anche se i tuoi colleghi non li eseguiranno. Almeno più tardi puoi dire: "Guarda! I miei test unitari lo hanno colto quando hai infranto il mio codice! Guarda quanto sono grandi i test unitari!" In tal caso, renderlo beffardo potrebbe valere la pena qui. (Anche se lo preferirei se l'oggetto potesse essere deriso senza definire un'interfaccia)
Winston Ewert

I test unitari non mi costringerebbero a creare (o utilizzare) un framework simulato in modo da non toccare le stored procedure live? Questo è il motivo principale per cui tendo a non aggiungere test: ognuno di noi ha una copia locale del database di produzione su cui testiamo e scriviamo il codice.
Wayne Molina,

3
@Anthony E il deridere giustifica sempre l'eccessiva complessità ambientale che comporta? Il deridere è uno strumento eccellente, ma anche la sua utilità deve essere ponderata rispetto ai costi e, a volte, la bilancia è ribaltata da un eccesso di riferimento indiretto. Certo, ci sono strumenti per aiutare con la complessità extra ma non faranno scomparire la complessità. Sembra esserci una tendenza crescente a trattare i "test a tutti i costi" come un dato di fatto. Credo che questo sia falso.
Konrad Rudolph,

Risposte:


75

Esiste un buon modo per arrivare a un compromesso tra la corretta progettazione del software e la non architettura eccessiva della soluzione?

YAGNI.

Potrei sacrificare un po 'di buon design

Falso presupposto.

e l'interfaccia di base e poi il singolo calcestruzzo,

Non è un "sacrificio". Questo è un buon design.


72
La perfezione si ottiene, non quando non c'è altro da aggiungere, ma quando non c'è più niente da togliere. Antoine De St-Exupery
Newtopian,

5
@Newtopian: le persone hanno sentimenti contrastanti riguardo agli ultimi prodotti Apple. :)
Roy Tinker,

14
Ho un grosso problema con questo tipo di risposte. "X è vero perché Y lo dice e Y è ben supportato dalla comunità" ragionamento valido? Se qualcuno nella vita reale si scagliasse contro YAGNI, gli mostreresti questa risposta come argomento?
Vemv,

2
@vemv. Nessuno qui sta sfidando contro YAGNI. Tutto ciò che S.Lott ha affermato è che la contraddizione percepita dal PO tra 2 obiettivi meritevoli è un errore. A proposito, conosci qualche sviluppatore attivo nel mondo del software di oggi che si scaglierebbe contro YAGNI? Se stai lavorando a progetti reali, sai che i requisiti cambiano costantemente e che utenti e gestori generalmente non sanno cosa fanno o non vogliono finché non li metti di fronte a loro. Implementare il necessario è MOLTO di lavoro: perché prendere tempo, energia e sprecare denaro (o rischiare il proprio lavoro) scrivendo un codice che cerca di prevedere il futuro?
Vettore

6
Il mio punto non riguarda YAGNI - riguarda la qualità della risposta. Non dico che qualcuno in particolare stia rantolando, faceva parte del mio ragionamento. Per favore, leggilo di nuovo.
Vemv,

74

Nella maggior parte dei casi, evitare il codice di cui non avrete bisogno porta a una progettazione migliore . Il design più sostenibile e a prova di futuro è quello che utilizza la minima quantità di codice semplice e ben definito che soddisfa i requisiti.

I disegni più semplici sono i più facili da evolvere. Nulla uccide la manutenibilità come inutili strati di astrazione ingegnerizzati.


8
Trovo 'evitare il codice YAGNI' in modo inquietantemente ambiguo. Potrebbe significare codice che non ti servirà o codice che aderisce al principio YAGNI . (Cfr. "Codice KISS")
vedi il

2
@sehe: Non è affatto ambiguo (mentre "aderire al principio YAGNI" è del tutto contraddittorio) se lo spieghi: cosa potrebbe significare "non avrai bisogno del codice", ma codifichi che non avrai bisogno?
Michael Borgwardt,

1
Stamperò questa risposta con un carattere grande e la appenderò in modo che tutti gli astronauti dell'architettura possano leggerla. +1!
kirk.burleson,

1
+1. Ricordo ancora il mio primo progetto aziendale che mi è stato assegnato per supportare e aggiungere alcune nuove funzionalità. La prima cosa che ho fatto è stato rimuovere 32.000 righe di codice inutile da un programma di 40.000 righe senza perdere alcuna funzionalità. Il programmatore originale fu licenziato poco dopo.
EJ Brennan,

4
@vemv: leggi "inutile" come "non attualmente utilizzato, se non banalmente", ovvero un caso di YAGNI. E "troppo ingegnerizzato" è un po 'più specifico di "cattivo". Nello specifico significa "complessità causata da concetti teorici o possibili requisiti immaginati, piuttosto che requisiti concreti e attuali".
Michael Borgwardt,

62

YAGNI e SOLID (o qualsiasi altra metodologia di progettazione) non si escludono a vicenda. Tuttavia, sono opposti quasi polari. Non è necessario aderire al 100% a nessuno dei due, ma ci sarà un po 'di dare e avere; più guardi un modello altamente astratto usato da una classe in un posto, dici YAGNI e lo semplifichi, meno SOLID diventa il design. Anche il contrario può essere vero; molte volte in fase di sviluppo, un progetto è implementato SOLIDAMENTE "sulla fede"; non vedi come ne avrai bisogno, ma hai solo un sospetto. Questo potrebbe essere vero (ed è sempre più probabile che sia vero più esperienza acquisisci), ma potrebbe anche metterti in un debito tecnico tanto quanto un approccio schiaffo-schiacciare; invece di una base di codice "spaghetti code" DIL, potresti finire con "lasagna code", avere così tanti livelli che semplicemente aggiungendo un metodo o un nuovo campo dati diventa un processo di giorni che passa attraverso proxy di servizio e dipendenze vagamente accoppiate con una sola implementazione. Oppure potresti finire con il "codice dei ravioli", che viene fornito in blocchi di dimensioni così ridotte che spostarsi verso l'alto, il basso, a sinistra o a destra nell'architettura ti porta attraverso 50 metodi con 3 linee ciascuno.

L'ho detto in altre risposte, ma eccolo qui: al primo passaggio, fallo funzionare. Al secondo passaggio, rendilo elegante. Al terzo passaggio, renderlo SOLID.

Abbattendo quello:

Quando scrivi per la prima volta una riga di codice, deve semplicemente funzionare. A questo punto, per quanto ne sai, è una tantum. Quindi, non ottieni alcun punto di stile per la costruzione di un'architettura a "torre d'avorio" per aggiungere 2 e 2. Fai quello che devi fare e presumi che non la vedrai mai più.

La prossima volta che il cursore si sposta su quella riga di codice, ora hai smentito le tue ipotesi da quando l'hai scritto per la prima volta. Stai rivisitando quel codice, probabilmente per estenderlo o usarlo altrove, quindi non è una tantum. Ora, alcuni principi di base come DRY (non ripeterti) e altre semplici regole per la progettazione del codice dovrebbero essere implementati; estrarre metodi e / o formare loop per codice ripetuto, estrarre variabili per letterali o espressioni comuni, magari aggiungere alcuni commenti, ma nel complesso il codice dovrebbe autocompattarsi. Ora, il tuo codice è ben organizzato, anche se forse strettamente accoppiato, e chiunque lo guardi può facilmente imparare cosa stai facendo leggendo il codice, invece di tracciarlo riga per riga.

La terza volta che il cursore inserisce quel codice, è probabilmente un grosso problema; o lo stai ancora estendendo di nuovo o è utile in almeno altri tre punti diversi nella base di codice. A questo punto, è un elemento chiave, se non fondamentale, del tuo sistema e dovrebbe essere progettato come tale. A questo punto, di solito hai anche la conoscenza di come è stato usato finora, il che ti consentirà di prendere buone decisioni di progettazione riguardo a come progettare il design per semplificare quegli usi e quelli nuovi. Ora le regole SOLIDI dovrebbero inserire l'equazione; estrarre classi contenenti codice con scopi specifici, definire interfacce comuni per tutte le classi che hanno scopi o funzionalità simili, impostare dipendenze vagamente accoppiate tra le classi e progettare dipendenze in modo da poterle aggiungere, rimuovere o scambiare facilmente.

Da questo punto in poi, se dovessi estendere, reimplementare o riutilizzare ulteriormente questo codice, è tutto ben confezionato ed estratto nel formato "scatola nera" che tutti conosciamo e amiamo; collegalo ovunque sia necessario o aggiungi una nuova variante al tema come nuova implementazione dell'interfaccia senza dover modificare l'utilizzo di detta interfaccia.


Secondo la meraviglia.
Filip Dupanović,

2
Sì. Quando ero solito progettare per il riutilizzo / l'estensione in anticipo, trovavo che quando volevo riutilizzarlo o estenderlo, sarebbe stato in un modo diverso da quello che avevo previsto. La previsione è difficile, soprattutto per quanto riguarda il futuro. Quindi sostengo la tua regola dei 3 strike: a quel punto, avrai un'idea ragionevole di come verrà riutilizzato / esteso. NB: un'eccezione è se sai già come sarà (ad es. Da progetti precedenti, conoscenza del dominio o già specificato).
13

Al quarto passaggio, rendilo FANTASTICO.
Richard Neil Ilagan,

@KeithS: Sembra che John Carmack abbia fatto qualcosa di simile: "il codice sorgente di Quake II ... unifica Quake 1, Quake World e QuakeGL in un'unica bellissima architettura di codice". fabiensanglard.net/quake2/index.php
13

+1 Penso che chiamerò la tua regola: "Refactoring pratico" :)
Songo,

31

Invece di uno di questi, preferisco WTSTWCDTUAWCROT?

(Qual è la cosa più semplice che possiamo fare che è utile e che possiamo rilasciare giovedì?)

Gli acronimi più semplici sono nel mio elenco di cose da fare, ma non sono una priorità.



2
Ho usato esattamente le lettere di cui avevo bisogno, né più né meno. In quel modo, sono un po 'come Mozart. Sì. Sono il Mozart degli acronimi.
Mike Sherrill 'Cat Recall',

4
Non ho mai saputo che Mozart fosse terribile nel creare acronimi. Imparo qualcosa di nuovo ogni giorno su un sito SE. : P
Cameron MacFarland,

3
@Mike Sherrill 'CatRecall': Forse si dovrebbe estenderlo a WTSTWCDTUWCROTAWBOF = "Qual è la cosa più semplice che possiamo fare che sia utile, possiamo rilasciare giovedì e non rompere venerdì?" ;-)
Giorgio

1
@ Stargazer712 no :) viola POLA.
v.oddou,

25

YAGNI e il buon design non sono in conflitto. YAGNI sta per (non) supportare le esigenze future. Una buona progettazione consiste nel rendere trasparente ciò che fa il tuo software in questo momento e come lo fa.

Introdurre una fabbrica renderà il tuo codice esistente più semplice? In caso contrario, non aggiungerlo. In tal caso, ad esempio quando si aggiungono test (cosa che si dovrebbe fare!), Aggiungerlo.

YAGNI significa non aggiungere complessità per supportare le funzioni future.
Il buon design consiste nel rimuovere la complessità pur supportando tutte le funzioni correnti.


15

Non sono in conflitto, i tuoi obiettivi sono sbagliati.

Cosa stai tentando di realizzare?

Vuoi scrivere software di qualità, e per farlo vuoi mantenere la tua base di codice piccola e non avere problemi.

Ora raggiungiamo un conflitto, come possiamo coprire tutti i casi se non scriviamo casi che non useremo?

Ecco come si presenta il tuo problema.

inserisci qui la descrizione dell'immagine (chiunque sia interessato, questo si chiama evaporazione delle nuvole )

Quindi, cosa sta guidando questo?

  1. Non sai di cosa non avrai bisogno
  2. Non vuoi perdere tempo e gonfiare il tuo codice

Quale di questi possiamo risolvere? Bene, sembra che non voler perdere tempo e il codice gonfio sia un grande obiettivo e abbia senso. E quella prima? Possiamo scoprire di cosa avremo bisogno per codificare?

Sto lavorando a un progetto sul lavoro e voglio introdurre lentamente buoni standard di codice ai miei colleghi (al momento non ce ne sono e tutto è solo un po 'hackerato insieme senza rima o motivo) [...] Ho fatto un passo indietro e ho pensato che stesse violando YAGNI perché so con certezza che non abbiamo bisogno della necessità di estendere alcune di queste classi.

Ridichiamo tutto questo

  • Nessun codice standard
  • Nessuna pianificazione del progetto in corso
  • I cowboy di tutto il mondo fanno le loro dannate cose (e stai provando a fare lo sceriffo nel selvaggio selvaggio west), sì.

Esiste un buon modo per arrivare a un compromesso tra la corretta progettazione del software e la non architettura eccessiva della soluzione?

Non hai bisogno di un compromesso, hai bisogno di qualcuno per gestire il team che è competente e ha visione dell'intero progetto. Hai bisogno di qualcuno in grado di pianificare ciò di cui hai bisogno, invece di ognuno di voi che getta in cose di cui NON avrai bisogno perché sei così incerto del futuro perché ... perché? Ti dirò perché, perché nessuno ha un dannato piano tra tutti voi. Stai cercando di introdurre standard di codice per risolvere un problema completamente separato. Il tuo problema PRIMARY che devi risolvere è una roadmap e un progetto chiari. Una volta ottenuto questo, puoi dire "gli standard di codice ci aiutano a raggiungere questo obiettivo in modo più efficace come squadra", che è la verità assoluta ma al di fuori dell'ambito di questa domanda.

Ottieni un project / team manager che può fare queste cose. Se ne hai uno, devi chiedere loro una mappa e spiegare il problema YAGNI che non presenta una mappa. Se sono gravemente incompetenti, scrivi tu stesso il piano e dì "ecco il mio rapporto per te sulle cose di cui abbiamo bisogno, per favore rivedi e facci sapere la tua decisione."


Non ne abbiamo uno, purtroppo. Il Responsabile dello sviluppo o è più preoccupato di fare le cose in fretta che di standard / qualità, o renderebbe le pratiche standard come l'uso di DataSet grezzi ovunque restituiti direttamente dalle classi, codifica in stile VB6 e tutto ciò che è in code-behind con logica duplicata copiata e incollata tutto al di sopra di.
Wayne Molina,

Va bene, sarà un po 'più lento, ma allora devi parlare con le loro preoccupazioni. Spiega che questo problema con YAGNI / codice inutile sta perdendo tempo, suggerisci la roadmap, dagli una roadmap, spiega che renderà il lavoro più veloce. Quando viene acquistato, entra con i problemi che gli standard scadenti hanno alla velocità di sviluppo, suggerisci quelli migliori. Vogliono che il progetto sia completato, semplicemente non sanno come gestire la cosa, o dovrai sostituirlo o farlo baby ... o smettere.
Incognito

Ehm ..... penso che l'obiettivo di guida sarebbe "vuoi avere un prodotto buono e prezioso"?
visto il

@sehe non è questa la sua decisione: p.
Incognito,

3
Bella risposta. Sta affrontando una battaglia in salita. Farlo sarà difficile se non ci sarà un buy-in da parte della direzione. E hai ragione, questa domanda è prematura e non affronta il vero problema.
Seth Spearman,

10

Consentire l'estensione del codice con unit test non sarà mai coperto da YAGNI, perché ne avrai bisogno. Tuttavia, non sono convinto che le modifiche apportate al tuo design a un'interfaccia a implementazione singola stiano effettivamente aumentando la testabilità del codice perché CustomerFactory eredita già da un'interfaccia e può comunque essere sostituita con una MockCustomerFactory in qualsiasi momento.


1
+1 per un commento utile sul problema reale piuttosto che sulla battaglia cosmica e / o l'amore tra Good Design e YAGNI.
ps

10

La domanda presenta un falso dilemma. La corretta applicazione del principio YAGNI non è qualcosa di indipendente. È un aspetto del buon design. Ciascuno dei principi SOLID sono anche aspetti di un buon design. Non puoi sempre applicare completamente ogni principio in qualsiasi disciplina. I problemi del mondo reale mettono molte forze sul tuo codice e alcuni di questi spingono in direzioni opposte. I principi del design devono tener conto di tutti questi aspetti, ma nessuna manciata di principi può adattarsi a tutte le situazioni.

Ora diamo un'occhiata a ciascun principio con la comprensione che mentre a volte possono andare in direzioni diverse, non sono in alcun modo intrinsecamente in conflitto.

YAGNI è stato concepito per aiutare gli sviluppatori a evitare un particolare tipo di rilavorazione: ciò che deriva dalla costruzione della cosa sbagliata. Lo fa guidandoci ad evitare di prendere decisioni errate troppo presto sulla base di ipotesi o previsioni su ciò che pensiamo cambierà o sarà necessario in futuro. L'esperienza collettiva ci dice che quando lo facciamo, di solito abbiamo torto. Ad esempio, YAGNI ti direbbe di non creare un'interfaccia ai fini della riusabilità , a meno che tu non sappia al momento che hai bisogno di più implementatori. Allo stesso modo YAGNI direbbe di non creare uno "ScreenManager" per gestire il singolo modulo in un'applicazione a meno che tu non sappia in questo momento che avrai più di uno schermo.

Contrariamente a quanto pensano molte persone, SOLID non riguarda la riusabilità, la genericità o persino l'astrazione. SOLID ha lo scopo di aiutarti a scrivere codice preparato per il cambiamento , senza dire nulla su ciò che potrebbe essere quel cambiamento specifico. I cinque principi di SOLID creano una strategia per la costruzione di un codice flessibile senza essere eccessivamente generico e semplice senza essere ingenuo. La corretta applicazione del codice SOLID produce classi piccole e mirate con ruoli e confini ben definiti. Il risultato pratico è che per ogni modifica dei requisiti necessari, è necessario toccare un numero minimo di classi. Allo stesso modo, per qualsiasi modifica del codice, esiste una quantità minima di "ripple" attraverso altre classi.

Guardando la situazione di esempio che hai, vediamo cosa potrebbero dire YAGNI e SOLID. Stai prendendo in considerazione un'interfaccia di repository comune a causa del fatto che tutti i repository sembrano uguali dall'esterno. Ma il valore di un'interfaccia comune e generica è la capacità di utilizzare uno degli implementatori senza la necessità di sapere quale sia in particolare. A meno che non ci sia un posto nella tua app in cui ciò sia necessario o utile, YAGNI afferma di non farlo.

Ci sono 5 principi SOLIDI da guardare. S è la sola responsabilità. Questo non dice nulla sull'interfaccia, ma potrebbe dire qualcosa sulle tue lezioni concrete. Si potrebbe argomentare che la gestione dell'accesso ai dati stesso potrebbe essere ritenuta una responsabilità di una o più altre classi, mentre la responsabilità dei repository è quella di tradurre da un contesto implicito (CustomerRepository è un repository implicitamente per le entità del Cliente) in chiamate esplicite al API di accesso ai dati generalizzata che specifica il tipo di entità cliente.

O è aperto-chiuso. Si tratta principalmente di eredità. Si applicherebbe se si cercasse di derivare i propri repository da una base comune che implementa funzionalità comuni o se si prevede che deriveranno ulteriormente dai diversi repository. Ma non lo sei, quindi non lo è.

L è sostituibilità di Liskov. Ciò vale se si intendeva utilizzare i repository tramite l'interfaccia del repository comune. Pone restrizioni sull'interfaccia e le implementazioni per garantire coerenza ed evitare una gestione speciale per diversi impelementer. La ragione di ciò è che tale gestione speciale mina lo scopo di un'interfaccia. Potrebbe essere utile considerare questo principio, perché potrebbe avvertirti di non utilizzare l'interfaccia del repository comune. Ciò coincide con la guida di YAGNI.

Sono la segregazione dell'interfaccia. Questo può valere se si inizia ad aggiungere diverse operazioni di query ai propri repository. La segregazione dell'interfaccia si applica laddove è possibile dividere i membri di una classe in due sottoinsiemi in cui uno verrà utilizzato da determinati consumatori e l'altro da altri, ma nessun consumatore probabilmente utilizzerà entrambi i sottoinsiemi. La guida è creare due interfacce separate, anziché una comune. Nel tuo caso, è improbabile che il recupero e il salvataggio di singole istanze vengano consumati dallo stesso codice che farebbe una query generale, quindi potrebbe essere utile separarli in due interfacce.

D è Iniezione delle dipendenze. Qui torniamo allo stesso punto di S. Se hai separato il tuo consumo dell'API di accesso ai dati in un oggetto separato, questo principio dice che piuttosto che semplicemente rinnovare un'istanza di quell'oggetto, dovresti passarlo quando crei un repository. Ciò semplifica il controllo della durata del componente di accesso ai dati, aprendo la possibilità di condividere riferimenti ad esso tra i repository, senza dover seguire la strada per renderlo un singleton.

È importante notare che la maggior parte dei principi SOLID non si applica necessariamente in questa fase particolare dello sviluppo della tua app. Ad esempio, se è necessario interrompere l'accesso ai dati dipende da quanto sia complicato e se si desidera testare la logica del repository senza colpire il database. Sembra che sia improbabile (purtroppo, secondo me), quindi probabilmente non è necessario.

Quindi, dopo tutte queste considerazioni, scopriamo che YAGNI e SOLID forniscono effettivamente un consiglio comune, solido e immediatamente rilevante: probabilmente non è necessario creare un'interfaccia di repository generica comune.

Tutto questo attento pensiero è estremamente utile come esercizio di apprendimento. Mentre impari richiede tempo, ma col tempo sviluppi intuizione e diventa molto veloce. Saprai la cosa giusta da fare, ma non dovrai pensare a tutte queste parole a meno che qualcuno non ti chieda di spiegare il perché.


Penso che la discussione in questa pagina abbia escluso 2 grandi contendenti "basso accoppiamento" e "alta coesione" dei Principi GRAS. Le decisioni di progettazione più costose derivano dal principio del "basso accoppiamento". Come quando "attivare" SRP + ISP + DIP per motivi di accoppiamento basso. Esempio: una classe -> 3 classi nel modello MVC. O ancora più costoso: suddiviso in moduli / assiemi .dll / .so. Questo è molto costoso a causa delle implicazioni di build, progetti, makelists, build server, aggiunte di file con versione sorgente ...
v.oddou

6

Sembra che credi che "un buon design" significhi seguire una sorta di idealogia e un insieme formale di regole che devono essere sempre applicate, anche se inutili.

IMO è un cattivo design. YAGNI è un componente di buon design, mai in contraddizione con esso.


2

Nel tuo esempio, direi che YAGNI dovrebbe prevalere. Non ti costerà molto se devi aggiungere interfacce in un secondo momento. A proposito, è davvero un buon progetto avere un'interfaccia per classe se non serve a niente?

Un altro pensiero, forse a volte ciò di cui hai bisogno non è un buon design ma un design sufficiente. Ecco una sequenza molto interessante di post sull'argomento:


Il tuo primo e il terzo link vanno nello stesso posto.
David Thornley,

2

Alcune persone sostengono che i nomi delle interfacce non dovrebbero iniziare con I. In particolare, uno dei motivi è che stai effettivamente perdendo la dipendenza dal fatto che il tipo dato sia una classe o un'interfaccia.

Cosa ti proibisce di CustomerFactoryessere una classe all'inizio e poi di cambiarla in un'interfaccia, che sarà implementata da DefaultCustormerFactoryo UberMegaHappyCustomerPowerFactory3000? L'unica cosa che dovresti cambiare è il luogo in cui l'implementazione ottiene istanze. E se hai un design meno buono, allora questo è un pugno di posti al massimo.

Il refactoring fa parte dello sviluppo. Meglio avere poco codice, facile da refactificare, piuttosto che avere un'interfaccia e una classe dichiarate per ogni singola classe, costringendoti a cambiare ogni nome di metodo in almeno due posizioni contemporaneamente.

Il vero punto di utilizzo delle interfacce è il raggiungimento della modularità, che è forse il pilastro più importante di un buon design. Si noti tuttavia che un modulo non è definito solo dal suo disaccoppiamento dal mondo esterno (anche se è così che lo percepiamo dalla prospettiva esterna), ma ugualmente dal suo funzionamento interno.
Ciò che intendo sottolineare è che disaccoppiare le cose, che intrinsecamente appartengono insieme, non ha molto senso. In un certo senso è come avere un armadio con un ripiano individuale per tazza.

L'importanza sta nel concepire un problema grande e complesso in sottoproblemi più piccoli e più semplici. E devi fermarti al punto in cui diventano abbastanza semplici senza ulteriori suddivisioni, altrimenti diventeranno di fatto più complicati. Questo potrebbe essere visto come un corollario di YAGNI. E sicuramente significa buon design.

L'obiettivo non è in qualche modo risolvere un problema locale con un singolo repository e una singola fabbrica. L'obiettivo è che questa decisione non abbia alcun effetto sul resto dell'applicazione. Ecco di cosa tratta la modularità.
Vuoi che i tuoi colleghi guardino il tuo modulo, vedano una facciata con una manciata di chiamate autoesplicative e si sentano sicuri, che possono usarli, senza doversi preoccupare di tutte le tubature interne potenzialmente sofisticate.


0

Stai creando interfaceanticipazioni multiple implementazioni in futuro. Potresti anche avere una I<class>per ogni classe nella tua base di codice. Non farlo.

Basta usare la singola classe di calcestruzzo secondo YAGNI. Se trovi che devi avere un oggetto "finto" a scopo di test, trasforma la classe originale in una classe astratta con due implementazioni, una con la classe concreta originale e l'altra con l'implementazione finta.

Dovrai ovviamente aggiornare tutte le istanze della classe originale per creare un'istanza della nuova classe concreta. Puoi aggirare il problema utilizzando un costruttore statico in primo piano.

YAGNI dice di non scrivere il codice prima che debba essere scritto.

Un buon design dice di usare l'astrazione.

Puoi avere entrambi. Una classe è un'astrazione.


0

Perché il marker si interfaccia? Mi colpisce che non stia facendo altro che "taggare". Con un "tag" diverso per ogni tipo di fabbrica, qual è il punto?

Lo scopo di un'interfaccia è quello di dare a una classe un "comportamento simile" al comportamento, per dare loro "capacità di repository" per così dire. Quindi, se tutti i tipi di repository concreti si comportano come lo stesso IRepository (implementano tutti IRepository), allora possono essere gestiti tutti allo stesso modo con un altro codice, con lo stesso identico codice. A questo punto il tuo design è estensibile. Aggiunta di più tipi di repository concreti, tutti gestiti come IRepository generici - lo stesso codice gestisce tutti i tipi di repository "generici".

Le interfacce servono per gestire le cose in base a elementi comuni. Ma le interfacce dei marker personalizzati a) non aggiungono alcun comportamento. e b) costringerti a gestire la loro unicità.

Nella misura in cui si progettano interfacce utili, si ottiene la qualità OO di non dover scrivere codice specializzato per gestire classi, tipi o interfacce di marcatori personalizzati che hanno una correlazione 1: 1 con classi concrete. Questa è ridondanza inutile.

Fuori mano vedo un'interfaccia marcatore se, ad esempio, avevi bisogno di una raccolta fortemente tipizzata di molte classi diverse. Nella collezione sono tutti "ImarkerInterface" ma quando li tiri fuori devi lanciarli nei loro tipi giusti.


0

Puoi, in questo momento, scrivere un repository ICustomer vagamente ragionevole? Ad esempio, (raggiungendo davvero qui, probabilmente un cattivo esempio) in passato i tuoi clienti hanno sempre usato PayPal? O tutti in compagnia sono in fermento per aver fatto amicizia con Alibaba? In tal caso, potresti voler utilizzare il design più complesso ora e apparire lungimirante ai tuoi capi. :-)

Altrimenti aspetta. Indovinare un'interfaccia prima di avere una o due implementazioni effettive di solito non riesce. In altre parole, non generalizzare / astrarre / utilizzare un modello di disegno elaborato fino a quando non avrai un paio di esempi da generalizzare.

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.