Principio di minimo stupore (POLA) e interfacce

17

Un buon quarto di secolo fa, quando stavo imparando il C ++, mi è stato insegnato che le interfacce dovevano essere indulgenti e, per quanto possibile, non mi importava dell'ordine che i metodi venivano chiamati poiché il consumatore potrebbe non avere accesso alla fonte o alla documentazione al posto di questo.

Tuttavia, ogni volta che ho istruito i programmatori junior e gli sviluppatori senior mi hanno sentito, hanno reagito con stupore che mi ha chiesto se fosse davvero una cosa o se fosse appena uscito di moda.

Chiaro come il fango?

Considera un'interfaccia con questi metodi (per la creazione di file di dati):

OpenFile
SetHeaderString
WriteDataLine
SetTrailerString
CloseFile

Ora, naturalmente, potresti semplicemente andare in ordine, ma tieni presente che non ti interessa il nome del file (pensa a.out ) o quale stringa di intestazione e trailer è stata inclusa, puoi semplicemente chiamare AddDataLine .

Un esempio meno estremo potrebbe essere omettere le intestazioni e i trailer.

Ancora un altro potrebbe impostare le stringhe di intestazione e trailer prima che il file sia stato aperto.

Questo è un principio del design dell'interfaccia riconosciuto o solo il modo POLA prima che venisse assegnato un nome?

NB. non impantanarti nei dettagli di questa interfaccia, è solo un esempio per il gusto di questa domanda.

    
posta Robbie Dee 14.04.2016 - 10:21
fonte

2 risposte

25

Un modo in cui è possibile attenersi al principio del minimo stupore è considerare altri principi come ISP e < a href="https://en.wikipedia.org/wiki/Single_responsibility_principle"> SRP o anche DRY .

Nell'esempio specifico che hai fornito, il suggerimento sembra essere che c'è una certa dipendenza dall'ordinare per manipolare il file; ma la tua API controlla sia l'accesso ai file che il formato dei dati, che odora un po 'come una violazione di SRP.

Modifica / Aggiorna: suggerisce anche che l'API stessa stia chiedendo all'utente di violare DRY, perché dovranno ripetere gli stessi passaggi ogni volta che usano l'API .

Considerare un'API alternativa in cui le operazioni IO sono separate dalle operazioni dei dati. e dove l'API stessa "possiede" l'ordine:

ContentBuilder

SetHeader( ... )
AddLine( ... )
SetTrailer ( ... )

FileWriter

Open(filename) 
Write(content) throws InvalidContentException
Close()

Con la separazione di cui sopra, ContentBuilder non ha bisogno di "fare" effettivamente qualcosa oltre a memorizzare le linee / intestazione / trailer (Forse anche un metodo ContentBuilder.Serialize() che conosce l'ordine). Seguendo altri principi SOLID, non importa più se si imposta l'intestazione o il trailer prima o dopo l'aggiunta di righe, poiché nulla in ContentBuilder viene effettivamente scritto nel file fino a quando non viene passato a FileWriter.Write .

Ha anche il vantaggio di essere un po 'più flessibile; ad esempio, potrebbe essere utile scrivere il contenuto in un registratore diagnostico, o magari passarlo su una rete invece di scriverlo direttamente su un file.

Durante la progettazione di un'API dovresti considerare anche la segnalazione degli errori, indipendentemente dal fatto che si tratti di uno stato, un valore di ritorno, un'eccezione, una richiamata o qualcos'altro. L'utente dell'API probabilmente si aspetta di essere in grado di rilevare a livello programmatico eventuali violazioni dei suoi contratti, o anche altri errori che non può controllare come errori di I / O dei file.

    
risposta data 14.04.2016 - 14:42
fonte
12

Non si tratta solo di POLA, ma anche di prevenire lo stato non valido come possibile fonte di bug.

Vediamo come possiamo fornire alcuni vincoli al tuo esempio senza fornire un'implementazione concreta:

Primo passaggio: non consentire che venga chiamato alcunché prima che un file sia stato aperto.

CreateDataFileInterface
  + OpenFile(filename : string) : DataFileInterface

DataFileInterface
  + SetHeaderString(header : string) : void
  + WriteDataLine(data : string) : void
  + SetTrailerString(trailer : string) : void
  + Close() : void

Ora dovrebbe essere ovvio che CreateDataFileInterface.OpenFile deve essere chiamato per recuperare un'istanza DataFileInterface , in cui i dati effettivi possono essere scritti.

Secondo passaggio: assicurati che le intestazioni e i rimorchi siano sempre impostati.

CreateDataFileInterface
  + OpenFile(filename : string, header: string, trailer : string) : DataFileInterface

DataFileInterface
  + WriteDataLine(data : string) : void
  + Close() : void

Ora devi fornire anticipatamente tutti i parametri richiesti per ottenere DataFileInterface : nomefile, intestazione e trailer. Se la stringa del trailer non è disponibile fino a quando non vengono scritte tutte le righe, puoi anche spostare questo parametro su Close() (eventualmente rinominando il metodo su WriteTrailerAndClose() ) in modo che il file non possa essere completato senza una stringa di trailer.

Per rispondere al commento:

I like separation of the interface. But I'm inclined to think that your suggestion about enforcement (e.g. WriteTrailerAndClose()) is verging on a violation of SRP. (This is something that I have struggled with on a number of occasions, but your suggestion seems to be a possible example.) How would you respond?

È vero. Non volevo concentrarmi più sull'esempio del necessario per chiarire il mio punto, ma è una buona domanda. In questo caso, penso che lo chiamerei Finalize(trailer) e sostengo che non fa troppo. La scrittura del trailer e la chiusura sono semplici dettagli di implementazione. Ma se non sei d'accordo o hai una situazione simile in cui è diverso, ecco una possibile soluzione:

CreateDataFileInterface
  + OpenFile(filename : string, header : string) : IncompleteDataFileInterface

IncompleteDataFileInterface
  + WriteDataLine(data : string) : void
  + FinalizeWithTrailer(trailer : string) : CompleteDataFileInterface

CompleteDataFileInterface
  + Close()

In realtà non lo farei per questo esempio, ma mostra come portare avanti la tecnica di conseguenza.

A proposito, ho assunto che i metodi in realtà debbano essere chiamati in quest'ordine, ad esempio per scrivere sequenzialmente molte righe. Se questo non è richiesto, preferirei sempre un costruttore, come suggerito da Ben Cottrel .

    
risposta data 14.04.2016 - 11:16
fonte

Leggi altre domande sui tag