Includere entrambe le versioni "attenta" e "pericolosa" di una funzione in una libreria / modulo

1

Sto scrivendo un codice che ho disaccoppiato in un modulo a parte, e anche se sono probabilmente l'unica persona che lo userà, sto cercando di pensare come se potessi non essere . Le funzioni in questo modulo eseguono operazioni sugli oggetti di un array che è passato e, al momento, sto facendo un sacco di controlli di sicurezza per evitare le eccezioni causate dall'ottenere dati errati - in particolare, tenendo conto della possibilità di un valore non definito / nullo per quella matrice, o uno degli oggetti nella matrice, o una delle proprietà su uno degli oggetti. In altre parole, un bel po 'di controllo. Tuttavia, nell'applicazione in cui viene utilizzato questo modulo, ho già controllato tutte queste cose prima di inviare i dati da elaborare da questo modulo, perché i valori nulli in questi dati causeranno problemi anche altrove.

Ora che tutto funziona senza intoppi, sono tentato di rimuovere alcuni dei controlli di sicurezza nel mio modulo per motivi di efficienza, dal momento che sto duplicando il lavoro. Tuttavia, se questo modulo fosse usato da qualcun altro, penserei che sarebbe meglio renderlo il più antiproiettile possibile - quindi se i dati cattivi vengono trasmessi dalla loro fine, il mio modulo sarà in grado di gestirlo senza essere responsabile per eventuali arresti anomali. Ma poi ho pensato che una persona ipotetica che utilizzava il mio modulo potesse trovarsi nella stessa posizione in cui mi trovo - certi che i dati che stanno trasmettendo siano buoni, perché lo hanno già controllato.

Per me, la soluzione ovvia è di avere due versioni di alcune funzioni chiave nel modulo: una versione "attenta" se non ti fidi dei dati, e una versione "pericolosa" se ti fidi di essa, e solo voglio massimizzare le prestazioni. È qualcosa che è fatto? Supponendo una buona documentazione, sarebbe una buona idea? E se fosse una buona idea, sarebbe meglio distinguere tra i due in base alla funzione, ad es. processSafely(data) e liveDangerously(data) ) - o tramite spazi dei nomi separati all'interno del modulo - myModule.safe.process(data) e myModule.reckless.process(data) ?

EDIT: le risposte finora sono state preziose, ma ho pensato di dover aggiungere (senza virare troppo nel territorio di Overflow dello stack) che il modulo specifico di cui sto parlando è progettato per accettare una (potenzialmente grande) schiera di blog articoli come oggetti (come verrebbero da un parser di un formato abbastanza standard), estraggono tag da detti articoli e li contano, e restituiscono un array contenente ogni singolo tag con il suo conteggio. In altre parole, come con tutte le attività che implicano l'analisi di file / oggetti presumibilmente conformi a un determinato formato, c'è un sacco di cose che possono andare storte, ma io (o l'utente) probabilmente dovrò render conto per quello altrove comunque (es. prima di rendere gli articoli ad una vista o qualsiasi altra cosa). Inoltre, tutti gli assegni di cui sto parlando sono O (n) - non solo una o due istruzioni preliminari if o le coercizioni di tipo.

    
posta Toadfish 11.02.2016 - 14:11
fonte

3 risposte

7

Secondo la mia esperienza, è davvero importante avere una funzione che controlli tutte le sue precondizioni anche quando "sai" tutto andrà bene. Tutto ciò che lasciamo al computer per noi, non possiamo rovinare e introdurre errori accidentali. Ci sono alcune tecniche su come questo può essere implementato in modo efficiente (ad es. Come asserzioni che possono essere disattivate per le build di produzione), ma ritengo che queste tendano a scambiare una notevole quantità di sicurezza con spesso poco guadagno.

Invece, possiamo provare a costruire astrazioni impermeabili dove un vincolo viene controllato una volta in fase di esecuzione, e il sistema di tipi della lingua dimostra che il vincolo non sarà mai invalidato.

Ad esempio, potrei avere una funzione che opera sugli indirizzi email: sendEmail(String to, String subject, String plainTextMessage) . Questa funzione dovrà ora verificare che String to sia un indirizzo email legale. Questo controllo verrà ripetuto inutilmente quando si inviano più e-mail allo stesso indirizzo. Invece, possiamo definire un class EmailAddress che verifica nel costruttore EmailAddress(String maybeAddress) se la stringa è un indirizzo legale. Altrimenti, getta. In tal caso, l'oggetto viene inizializzato con quell'indirizzo. Questo controllo viene eseguito una volta alla costruzione, in seguito è possibile utilizzarlo senza ulteriori controlli.

Tuttavia, avremo bisogno di un accesso esterno alla stringa sottostante. È importante che questo accesso sia di sola lettura. Se il tipo sottostante è mutabile o se la tua lingua non supporta i riferimenti const, può avere senso indirizzare i metodi sicuri all'oggetto piuttosto che esporlo. Dopo averlo fatto, potremmo finire con sendEmail(EmailAddress to, EmailSubject subject, String body) .

Questa tecnica è particolarmente applicabile nei linguaggi tipizzati staticamente con buoni meccanismi di incapsulamento dove la definizione di un nuovo tipo è facile da fare. Io uso abitualmente questa tecnica in C ++ e occasionalmente in Java. Nei linguaggi dinamici, è meno utile poiché non ci sono controlli di tipo statico. Tuttavia, la funzione di consumo ora deve solo eseguire un controllo del tipo di runtime, che di solito è meno costoso del controllo di precondizione completo.

    
risposta data 11.02.2016 - 14:52
fonte
1

Se possibile, il modo migliore per gestirlo è codificare le precondizioni nel sistema di tipi. Quindi assicurati che l'unico modo per avere un'istanza del tuo tipo di asserzione preliminare sia tramite una funzione che costringe il chiamante a gestire l'errore, ad esempio restituendo un tipo Option / Optional / Maybe .

Il vantaggio di questo è che il codice che viola le precondizioni del tuo metodo non può essere compilato.

Ad esempio, in scala,

def toPosInt(n: Int): Option[Int] = if (n > 0) Some(n) else None
// Find the square root of a positive number
def sqrt(n: PosInt): Float = ???

Non è possibile chiamare questo sqrt con un numero negativo perché non è possibile ottenere un POSInt per un numero negativo. Java 8 ha un tipo Optional simile e così via.

    
risposta data 11.02.2016 - 15:13
fonte
1

Ci sono alcune possibilità:

1) Soldato acceso, analizza ogni cosa che puoi e non lamentarti mai. Ignora tutti quegli 404, HTML errato e null. Questa è una scelta terribile. Un sacco di lavoro per te, e il cliente non ha idea che i risultati potrebbero non essere quello che pensano che dovrebbero essere. Se, ad esempio, i blog che collegano Bernie hanno più errori dei blog che lodano Hillary, i tuoi risultati saranno di parte.

Un'ottima citazione da una strong critica di PHP sul perché non dovresti essere un soldato su "" Di fronte a qualcosa di insensato o di abortire con un errore, farà qualcosa di insensato "

2) Genera un'eccezione utile sul primo input non valido. Più facile da codificare, più utile per il cliente. Ma ancora fastidioso: difficile per loro risolverne uno alla volta.

3) Soldato acceso, ma ignorare i "blog cattivi". Tuttavia, tieni un elenco di tutti i blog non validi (e idealmente una descrizione, ad esempio "link has a 404") e restituiscilo al client. Ricevono feedback che ci sono errori e possono decidere se i loro risultati sono "abbastanza buoni". Inoltre puoi testare l'unità includendo alcuni noti input non validi.

    
risposta data 11.02.2016 - 19:24
fonte

Leggi altre domande sui tag