Principio del minimo stupore (POLA) e interfacce


17

Un buon quarto di secolo fa, quando stavo imparando il C ++, mi hanno insegnato che le interfacce dovrebbero essere perdonanti e, per quanto possibile, non preoccuparsi dell'ordine che sono stati chiamati i metodi poiché il consumatore potrebbe non avere accesso alla fonte o alla documentazione al posto di Questo.

Tuttavia, ogni volta che ho fatto da mentore ai programmatori junior e agli sviluppatori senior mi hanno ascoltato, hanno reagito con stupore, il che mi ha fatto chiedere se questa fosse davvero una cosa o se fosse appena passata di moda.

Chiaro come il fango?

Prendi in considerazione un'interfaccia con questi metodi (per la creazione di file di dati):

OpenFile
SetHeaderString
WriteDataLine
SetTrailerString
CloseFile

Ora puoi ovviamente passare attraverso questi in ordine, ma dire che non ti interessa il nome del file (pensa a.out) o quale intestazione e stringa del trailer sono stati inclusi, puoi semplicemente chiamare AddDataLine.

Un esempio meno estremo potrebbe essere l'omissione delle intestazioni e dei trailer.

Ancora un altro potrebbe essere l'impostazione delle stringhe di intestazione e trailer prima dell'apertura del file.

È questo un principio di progettazione dell'interfaccia che viene riconosciuto o solo il modo POLA prima che gli venisse dato un nome?

NB non impantanarti nelle minuzie di questa interfaccia, è solo un esempio per il bene di questa domanda.


10
Il principio "minimo stupore" è molto più diffuso nella progettazione dell'interfaccia utente rispetto alla progettazione "interfaccia di programmazione applicativa". Il motivo è che non ci si può aspettare che un utente di un sito Web o di un programma legga le istruzioni prima di utilizzarlo, mentre ci si aspetta che un programmatore, almeno in linea di principio, legga i documenti API prima di programmarli.
Kilian Foth,


7
@KilianFoth: Sono abbastanza sicuro che Wikipedia abbia torto su questo - POLA non riguarda solo la progettazione dell'interfaccia utente, il termine "principio della minima sorpresa" (che è abbastanza lo stesso) è anche usato da Bob Martin per la progettazione di funzioni e di classe nel suo Libro "Codice pulito".
Doc Brown,

2
Spesso, un'interfaccia immutabile è comunque migliore. È possibile specificare tutti i dati che si desidera impostare in fase di costruzione. Non sono rimaste ambiguità e la classe diventa più semplice da scrivere. (A volte questo schema non è possibile, ovviamente.)
usr

4
Non sono completamente d'accordo sul fatto che POLA non si applichi alle API. Si applica a tutto ciò che un essere umano crea per altri umani. Quando le cose si comportano come previsto, sono più facili da concettualizzare e quindi creano un carico cognitivo inferiore, permettendo alle persone di fare più cose con meno sforzo.
Gort il robot

Risposte:


25

Un modo in cui è possibile attenersi al principio del minimo stupore è quello di considerare altri principi come ISP e SRP , o addirittura DRY .

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

Modifica / Aggiorna: suggerisce anche che l'API stessa sta 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 di I / O sono separate dalle operazioni di dati. e dove l'API stessa "possiede" l'ordinamento:

ContentBuilder

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

FileWriter

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

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

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

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


Esattamente quello che stavo cercando - grazie! Dall'articolo del provider di servizi Internet: "(ISP) afferma che nessun client dovrebbe essere costretto a dipendere da metodi che non utilizza"
Robbie Dee,

5
Questa non è una cattiva risposta, tuttavia il generatore di contenuti potrebbe essere implementato in un modo in cui l'ordine delle chiamate SetHeadero AddLineconta. Per eliminare questa dipendenza dell'ordine non è né ISP né SRP, è semplicemente POLA.
Doc Brown,

Quando l'ordine conta, è ancora possibile soddisfare POLA definendo le operazioni in modo tale che l'esecuzione di passaggi successivi richieda un valore restituito da passaggi precedenti, applicando così l'ordine con il sistema di tipi. FileWriterpotrebbe quindi richiedere il valore dell'ultimo ContentBuilderpassaggio del Writemetodo per garantire che tutto il contenuto di input sia completo, rendendo InvalidContentExceptionsuperfluo.
Dan Lyons,

@DanLyons Sento che è piuttosto vicino alla situazione che il richiedente sta cercando di evitare; dove l' utente dell'API deve conoscere o preoccuparsi dell'ordine. Idealmente, l'API stessa dovrebbe far rispettare l'ordine, altrimenti potrebbe potenzialmente chiedere all'utente di violare il DRY. Questo è il motivo per dividere ContentBuildere consentire FileWriter.Writedi incapsulare quel po 'di conoscenza. L'eccezione sarebbe necessaria nel caso in cui qualcosa dovesse non funzionare con il contenuto (ad es. Un'intestazione mancante). Anche un ritorno potrebbe funzionare, ma non sono un fan di trasformare le eccezioni in codici di ritorno.
Ben Cottrell,

Ma sicuramente vale la pena aggiungere altre note su DRY e ordinare alla risposta.
Ben Cottrell,

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 passo: non consentire che venga chiamato nulla prima dell'apertura di un file.

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.OpenFiledeve essere chiamato per recuperare DataFileInterfaceun'istanza, in cui è possibile scrivere i dati effettivi.

Secondo passaggio: assicurarsi che le intestazioni e i trailer siano sempre impostati.

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

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

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


Per rispondere al commento:

Mi piace la separazione dell'interfaccia. Ma sono propenso a pensare che il tuo suggerimento sull'applicazione (ad esempio WriteTrailerAndClose ()) stia rasentando una violazione di SRP. (Questo è qualcosa con cui ho lottato in diverse occasioni, ma il tuo suggerimento sembra essere un possibile esempio.) Come risponderesti?

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 direi che non fa troppo. Scrivere il trailer e chiudere 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 a termine la tecnica di conseguenza.

A proposito, ho ipotizzato che i metodi in realtà debbano essere chiamati in questo ordine, ad esempio per scrivere sequenzialmente molte righe. Se ciò non fosse necessario, preferirei sempre un costruttore, come suggerito da Ben Cottrel .


1
Ahimè, sei caduto nella trappola che ti avevo avvertito esplicitamente di evitare fin dall'inizio. Non è richiesto il nome di un file, né l'intestazione e il trailer. Ma il tema generale della divisione dell'interfaccia è buono, quindi +1 :-)
Robbie Dee,

Oh, allora ti ho frainteso, ho pensato che questo stesse descrivendo l'intenzione dell'utente, non l'implementazione.
Fabian Schmengler,

Mi piace la separazione dell'interfaccia. Ma sono propenso a pensare che il tuo suggerimento sull'applicazione (ad es. WriteTrailerAndClose()) Stia rasentando una violazione dell'SRP. (Questo è qualcosa con cui ho lottato in diverse occasioni, ma il tuo suggerimento sembra essere un possibile esempio.) Come risponderesti?
kmote

1
La risposta di @kmote era troppo lunga per un commento, vedi il mio aggiornamento
Fabian Schmengler,

1
Se il nome del file è facoltativo, è possibile fornire un OpenFilesovraccarico che non ne richiede uno.
5gon12eder
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.