Alcune aspettative di comportamento possono essere parte del contratto di un'interfaccia?

2

Dato il seguente codice:

public interface IUserDataProvider {        
    ICollection<SomeClass> getSomeClassRecordsForUser(int idUser);
}

È possibile definire (tramite documentazione) un'aspettativa che ogni classe di implementazione restituisca null invece di una raccolta vuota (o viceversa) quando non vengono trovati record? Mi rendo conto che le interfacce non hanno il controllo esplicito sul modo in cui sono implementate, ma considerando che le interfacce si dice definiscano un contratto, mi chiedo quanto possa giustamente estendersi la giurisdizione di quel contratto.

Grazie in anticipo per le tue risposte.

    
posta Patrick 02.08.2013 - 22:59
fonte

3 risposte

6

La libreria standard documenta anche contratti rigorosi per le sue interfacce. Come Comparable che ha una lista abbastanza lunga di requisiti comportamentali.

Di solito ci sono sempre dei contratti impliciti quando hai un'interfaccia. Quando, ad esempio, hai un metodo getNewestRecord() , probabilmente c'è un contratto (non scritto?) Che restituisce davvero il più recente, anche se un'implementazione potrebbe tecnicamente restituire qualsiasi record.

Ma quando crei API per altre persone da utilizzare, generalmente è meglio seguire il principio della sorpresa minima. Il comportamento restituito da null quando non vengono trovati record può essere sorprendente per un utente e l'implementatore dell'interfaccia. Un design migliore sarebbe definire il metodo di interfaccia con throws NoRecordsFoundException . In questo modo dovrebbe essere chiaro sia agli implementatori sia ai consumatori quale comportamento ci si aspetta in quel caso. Inoltre, il consumatore difficilmente può dimenticare di gestire correttamente questo caso, poiché è richiesto un blocco try / catch. Questo probabilmente ti salverà da alcune NullPointerExceptions.

    
risposta data 02.08.2013 - 23:10
fonte
1

Naturalmente è possibile documentare il comportamento atteso dell'interfaccia in prosa. Ma dal momento che il comportamento lungo è documentato solo in prosa, non vi è alcuna garanzia che le implementazioni soddisfino effettivamente questi requisiti.

In questo momento posso pensare a due modi alternativi di specificare il comportamento previsto in modo tale da renderlo verificabile / verificabile:

  1. Specifica il comportamento previsto come test unitario e rendi i test unitari disponibili all'utente finale:

    @Test public void getSomeClassRecordsForUser_NeverReturnsAnEmptyCollection() {
        IUserDataProvider provider = …;
        Collection<SomeClass> result = provider.getSomeClassRecordsForUser(…);
        Assume.assumeNotNull(result);
        Assert.assertTrue(result.count() > 0);
    }
    

    Ho appreso di questo approccio dal libro, "Oggetto in crescita- Software orientato dai test orientati ". Fondamentalmente, le interfacce sono caratterizzate da contratti "statici" ("Quali parti sono costituite da un componente?"), Con i test unitari come controparti "dinamiche" ("Come interagisce un componente con gli altri?").

  2. Utilizza Design by Contract. Alcune lingue supportano questo direttamente (ad esempio Eiffel), altre lo supportano tramite una libreria e / o estensioni del compilatore; per esempio. con i contratti di codice di .NET:

    [ContractClassFor(typeof(IUserDataProvider))]
    public class IUserDataProviderContract
    {
        public ICollection<SomeClass> GetSomeClassRecordsForUser(…)
        {
            Contract.Ensure(result == null || result.Count > 0);
        }
    }
    

    (Ogni volta che l'esecuzione del programma attraversa un limite del metodo, vengono verificate le pre-condizioni e le post-condizioni. A seconda della lingua e / o della libreria utilizzata, questi controlli possono anche essere eseguiti in fase di compilazione.)

risposta data 03.08.2013 - 00:09
fonte
0

Parole di parole semplici:

  • Una raccolta vuota è più elegante perché lo stesso codice che si usa per attraversare una collezione popolata, funziona bene con una vuota, solo che nulla viene elaborato. Esempio: a for each loop .

  • Una raccolta vuota è un oggetto valido . Non avere oggetti fa parte dello stato valido di una raccolta.

  • Se non ci sono articoli che violano le regole aziendali , quindi aggiungi una clausola throw a NoDataFoundException alla firma del metodo nell'interfaccia

  • Se per qualche motivo vuoi davvero che il metodo sia return null nel caso in cui non vengano trovati record, quindi dai un nome al metodo getSomeClassRecordsForUserOrNull() , ma non lo consiglio .

EDIT: gli ultimi due punti sono modi per far sì che gli implementors sappiano che ti aspetti nulla invece di una raccolta vuota.

    
risposta data 02.08.2013 - 23:30
fonte

Leggi altre domande sui tag