Programmazione per uso futuro di interfacce

41

Ho un collega seduto accanto a me che ha progettato un'interfaccia come questa:

public interface IEventGetter {

    public List<FooType> getFooList(String fooName, Date start, Date end)
        throws Exception;
    ....

}

Il problema è che, in questo momento, non stiamo usando questo parametro di "fine" in nessuna parte del nostro codice, è solo lì perché potremmo doverlo usare un po 'di tempo in futuro.

Stiamo cercando di convincerlo che è una cattiva idea mettere i parametri in interfacce che non servono in questo momento, ma continua a insistere sul fatto che molto lavoro dovrà essere fatto se implementiamo l'uso di "end" data qualche tempo dopo e devi adattare tutto il codice allora.

Ora, la mia domanda è, ci sono fonti che gestiscono un argomento come questo di guru di codifica "rispettati" ai quali possiamo collegarlo?

    
posta sveri 11.07.2014 - 09:58
fonte

9 risposte

61

Invitalo a conoscere YAGNI . La parte Razionale della pagina di Wikipedia può essere particolarmente interessante qui:

According to those who advocate the YAGNI approach, the temptation to write code that is not necessary at the moment, but might be in the future, has the following disadvantages:

  • The time spent is taken from adding, testing or improving the necessary functionality.
  • The new features must be debugged, documented, and supported.
  • Any new feature imposes constraints on what can be done in the future, so an unnecessary feature may preclude needed features from being added in the future.
  • Until the feature is actually needed, it is difficult to fully define what it should do and to test it. If the new feature is not properly defined and tested, it may not work correctly, even if it eventually is needed.
  • It leads to code bloat; the software becomes larger and more complicated.
  • Unless there are specifications and some kind of revision control, the feature may not be known to programmers who could make use of it.
  • Adding the new feature may suggest other new features. If these new features are implemented as well, this could result in a snowball effect towards feature creep.

Altri possibili argomenti:

  • "L'80% del costo della vita di un software va alla manutenzione" . Scrivere codice just in time riduce il costo della manutenzione: si deve mantenere meno codice e può concentrarsi sul codice effettivamente necessario.

  • Il codice sorgente viene scritto una volta, ma letto decine di volte. Un argomento addizionale, non usato da nessuna parte, porterebbe a tempo sprecato a capire perché c'è un argomento che non è necessario. Dato che questa è un'interfaccia con diverse possibili implementazioni, rende le cose solo più difficili.

  • Il codice sorgente dovrebbe essere auto-documentante. La firma effettiva è fuorviante, poiché un lettore potrebbe pensare che end influenzi il risultato o l'esecuzione del metodo.

  • Le persone che scrivono le implementazioni concrete di questa interfaccia potrebbero non capire che l'ultimo argomento non dovrebbe essere usato, il che porterebbe a diversi approcci:

    1. Non ho bisogno di end , quindi semplicemente ignorerò il suo valore,

    2. Non ho bisogno di end , quindi genererò un'eccezione se non è null ,

    3. Non ho bisogno di end , ma proverò a usarlo in qualche modo,

    4. Scriverò un sacco di codice che potrebbe essere utilizzato in seguito quando sarà necessario end .

Ma ricorda che il tuo collega potrebbe avere ragione.

Tutti i punti precedenti sono basati sul fatto che il refactoring è facile, quindi aggiungere un argomento in seguito non richiederà molto sforzo. Ma questa è un'interfaccia e, come interfaccia, può essere utilizzata da diversi team che contribuiscono ad altre parti del tuo prodotto. Ciò significa che la modifica di un'interfaccia potrebbe essere particolarmente dolorosa, nel qual caso YAGNI non si applica proprio qui.

La risposta di hjk offre una buona soluzione: l'aggiunta di un metodo a un'interfaccia già utilizzata non è particolarmente difficile , ma in alcuni casi ha anche un costo considerevole:

  • Alcuni framework non supportano sovraccarichi. Ad esempio, se ricordo bene (correggimi se ho torto), WCF di .NET non supporta i sovraccarichi.

  • Se l'interfaccia ha molte implementazioni concrete, aggiungere un metodo all'interfaccia richiederebbe tutte le implementazioni e aggiungendo anche il metodo.

risposta data 11.07.2014 - 11:21
fonte
26

but he keeps on insisting that a lot of work will have to be done if we implement the use of "end" date some time later and have to adapt all the code then.

(qualche tempo dopo)

public class EventGetter implements IEventGetter {

    private static final Date IMPLIED_END_DATE_ASOF_20140711 = new Date(Long.MAX_VALUE); // ???

    @Override
    public List<FooType> getFooList(String fooName, Date start) throws Exception {
        return getFooList(fooName, start, IMPLIED_END_DATE_ASOF_20140711);
    }

    @Override
    public List<FooType> getFooList(String fooName, Date start, Date end) throws Exception {
        // Final implementation goes here
    }
}

Questo è tutto ciò di cui hai bisogno, l'overloading dei metodi. Il metodo aggiuntivo in futuro può essere introdotto in modo trasparente senza influire sulle chiamate al metodo esistente.

    
risposta data 11.07.2014 - 11:35
fonte
18

[Are there] "respected" coding gurus that we can link him to [to persuade him]?

Gli appelli all'autorità non sono particolarmente convincenti; meglio presentare un argomento valido indipendentemente da chi lo abbia detto.

Ecco una reductio ad absurdum che dovrebbe convincere o dimostrare che il tuo collega è bloccato ad essere "giusto" indipendentemente dalla sensibilità:

Ciò di cui hai veramente bisogno è

getFooList(String fooName, Date start, Date end, Date middle, 
           Date one_third, JulianDate start, JulianDate end,
           KlingonDate start, KlingonDate end)

Non sai mai quando dovrai internazionalizzare per Klingon, quindi faresti meglio a prenderti cura ora perché richiederà molto lavoro per il retrofit ei Klingon non sono noti per la loro pazienza.

    
risposta data 11.07.2014 - 10:52
fonte
12

Da un punto di vista dell'ingegneria del software, credo che la soluzione adeguata per questo tipo di problemi sia nel modello di builder. Questo è sicuramente un link dagli autori di "guru" per il tuo collega link .

Nel modello di builder, l'utente crea un oggetto che contiene i parametri. Questo contenitore di parametri verrà quindi passato al metodo. Questo si prenderà cura di eventuali estensioni e sovraccarichi di parametri che il tuo collega avrebbe bisogno in futuro, rendendo l'intera cosa molto stabile quando è necessario apportare modifiche.

Il tuo esempio diventerà:

public interface IEventGetter {
    public List<FooType> getFooList(ISearchFooRequest req) {
        throws Exception;
    ....
    }
}

public interface ISearchFooRequest {
        public String getName();
        public Date getStart();
        public Date getEnd();
        public int getOffset();
        ...
    }
}

public class SearchFooRequest implements ISearchFooRequest {

    public static SearchFooRequest buildDefaultRequest(String name, Date start) {
        ...
    }

    public String getName() {...}
    public Date getStart() {...}
    ...
    public void setEnd(Date end) {...}
    public void setOffset(int offset) {...}
    ...
}
    
risposta data 11.07.2014 - 13:15
fonte
7

Non ne hai bisogno ora, quindi non aggiungerlo. Se ne hai bisogno in seguito, estendi l'interfaccia:

public interface IEventGetter {

    public List<FooType> getFooList(String fooName, Date start)
         throws Exception;
    ....

}

public interface IBoundedEventGetter extends IEventGetter {

    public List<FooType> getFooList(String fooName, Date start, Date end)
        throws Exception;
    ....

}
    
risposta data 11.07.2014 - 18:38
fonte
4

Nessun principio di progettazione è assoluto, quindi mentre per lo più sono d'accordo con le altre risposte, ho pensato di interpretare l'avvocato del diavolo e discutere alcune condizioni in base alle quali prenderei in considerazione l'idea di accettare la soluzione del collega:

  • Se si tratta di un'API pubblica e prevedi che la funzione sia utile per gli sviluppatori di terze parti, anche se non la utilizzi internamente.
  • Se ha notevoli benefici immediati, non solo quelli futuri. Se la data di fine implicita è Now() , l'aggiunta di un parametro rimuove un effetto collaterale, che presenta vantaggi per la memorizzazione nella cache e il test delle unità. Forse consente un'implementazione più semplice. O forse è più coerente con altre API nel tuo codice.
  • Se la tua cultura dello sviluppo ha una storia problematica. Se i processi o la territorialità rendono troppo difficile cambiare qualcosa di centrale come un'interfaccia, allora quello che ho visto prima è che le persone implementano soluzioni alternative sul lato client invece di cambiare l'interfaccia, quindi stai cercando di mantenere una dozzina di date finali ad hoc filtri invece di uno. Se questo genere di cose accade molto alla tua azienda, ha senso mettere un po 'più di impegno nelle prove future. Non fraintendermi, è migliore cambiare i tuoi processi di sviluppo, ma di solito è più facile a dirsi che a farsi.

Detto questo, nella mia esperienza il più grande corollario di YAGNI è YDKWFYN: non sai quale forma ti servirà (sì, ho appena fatto l'acronimo). Anche se il parametro di limite alcuni potrebbe essere relativamente prevedibile, potrebbe assumere la forma di un limite di pagina, o numero di giorni o un valore booleano che specifica di utilizzare la data di fine da una tabella delle preferenze utente o qualsiasi numero di cose

Poiché non hai ancora il requisito, non hai modo di sapere quale tipo dovrebbe essere quel parametro. Spesso finisci per avere un'interfaccia imbarazzante che non è proprio la più adatta alle tue esigenze, o doverla comunque modificare.

    
risposta data 11.07.2014 - 18:54
fonte
2

Non ci sono abbastanza informazioni per rispondere a questa domanda. Dipende da cosa fa in realtà getFooList e come lo fa.

Ecco un ovvio esempio di un metodo che dovrebbe supportare un parametro aggiuntivo, usato o meno.

void CapitalizeSubstring (String capitalize_me, int start_index);

L'implementazione di un metodo che opera su una raccolta in cui è possibile specificare l'inizio ma non la fine della raccolta è spesso sciocco.

Devi davvero guardare al problema stesso e chiedere se il parametro non ha senso nel contesto dell'intera interfaccia, e davvero quanto peso impone il parametro aggiuntivo.

    
risposta data 11.07.2014 - 23:22
fonte
1

Temo che il tuo collega possa effettivamente avere un punto molto valido. Anche se la sua soluzione non è in realtà la migliore.

Dalla sua interfaccia proposta è chiaro che

public List<FooType> getFooList(String fooName, Date start, Date end) throws Exception;

sta restituendo istanze trovate in un intervallo nel tempo. Se attualmente i client non utilizzano il parametro end, ciò non cambia il fatto che si aspettino che le istanze vengano trovate in un intervallo nel tempo. Significa solo che attualmente tutti i client utilizzano intervalli aperti (dall'inizio alla fine)

Quindi un'interfaccia migliore sarebbe:

public List<FooType> getFooList(String fooName, Interval interval) throws Exception;

Se fornisci intervallo con un metodo factory statico:

public static Interval startingAt(Date start) { return new Interval(start, null); }

allora i clienti non sentiranno nemmeno la necessità di specificare un orario di fine.

Allo stesso tempo la tua interfaccia trasmette correttamente ciò che fa, dal momento che getFooList(String, Date) non comunica che questo riguarda un intervallo.

Si noti che il mio suggerimento deriva da ciò che il metodo attualmente fa, non da ciò che dovrebbe o potrebbe fare in futuro, e in quanto tale il principio YAGNI (che è davvero molto valido) non si applica qui.

    
risposta data 16.07.2014 - 01:36
fonte
0

L'aggiunta di un parametro non utilizzato è confusa. Le persone potrebbero chiamare quel metodo presumendo che questa funzionalità funzioni.

Non lo aggiungerei. È banale aggiungerlo in seguito utilizzando un refactoring e aggiustando meccanicamente i siti di chiamata. In un linguaggio tipizzato staticamente questo è facile da fare. Non è necessario aggiungerlo avidamente.

Se è un motivo per avere quel parametro puoi evitare la confusione: aggiungi un assert nell'implementazione di quell'interfaccia per far sì che questo parametro venga passato come valore predefinito. Se qualcuno utilizza per errore quel parametro, noterà immediatamente durante il test che questa funzione non è implementata. Questo elimina il rischio di far scivolare un bug nella produzione.

    
risposta data 12.07.2014 - 00:07
fonte

Leggi altre domande sui tag