2015-12-05 9 views
15

Questa domanda riguarda le convenzioni di codifica, le migliori pratiche e lo stile nella produzione, codice Common-Lisp mission-critical. Ho consultato la Guida di stile Common-Lisp di Google (http://tinyurl.com/qfvnqcx), ma non ho trovato nulla che riguardasse chiaramente la mia specifica preoccupazione, che esprimo con l'esempio e al contrario con C/C++, Java, ecc. Ho anche dato una rapida occhiata a Common- Il codice Lisp si basa su Github e non ho visto molti controlli degli argomenti e controllo dei valori intermedi, che vedo in C/C++, Java, ecc.Idiomi di Lisp comuni per il controllo degli argomenti e altre paranoie?

Nel mio negozio, siamo molto abituati a controllare gli argomenti & altri valori e uscite anticipate quando gli argomenti non soddisfano i contratti/condizioni preliminari, ecc. Per esempio, considera il seguente (micro-esempio, forzato, critico, tipico, ma non-per-sprecare tempo-rifiuto, che presagisce l'esempio CL):

ErrorCode o_symb_to_g_symb (char * symb, uint len) 
{ if (len < 2) { return ERROR_LENGTH; } 
    if (symb[0] != 'O' || symb[1] != '!') { return ERROR_SYNTAX; } 
    char * result = (char *) malloc (len + 1); 
    if (NULL == result) { return ERROR_MALLOC; } 
    if (result != strncpy (result, symb, len + 1)) 
    { return ERROR_STRNCPY; } 
    result[0] = 'G'; 
    return result; } 

Questo fa approssimativamente la stessa cosa del codice di Doug Hoyte da "Let Over Lambda", pagina 67, solo si cura di controllare il più possibile lungo il percorso (http://letoverlambda.com/).

(defun o!-symbol-to-g!-symbol (s) 
    (symb "G!" 
      (subseq (symbol-name s) 2)))) 

La domanda è se il codice di produzione reale in Common Lisp esegue un controllo maggiore. Ad esempio, potrebbe essere ragionevole scrivere codice esplicito per verificare che s sia effettivamente una stringa ed è in realtà abbastanza lunga, e in realtà ha un "O!" come i suoi primi due personaggi.

Questo codice scavalca tutta quella paranoia solo perché è pedagogico? Lo stesso codice in una distribuzione di produzione mission-critical avrebbe più probabilità di avere i controlli di paranoia (la mia luce su Github per il codice CL suggerirebbe "no")? Se il codice CL del mondo reale non tende al paranoico, perché no? La pratica del test d'angolo o dei test esaurienti è più diffusa di quanto non appaia?

In breve, sono abbastanza perplesso dalla differenza di stili. Il codice C del mondo reale e mission-critical tende ad essere super-paranoico. Non vedo lo stesso in CL. Forse non sto guardando le giuste basi di codice? Forse non ho letto i libri giusti? Una risposta a questa domanda non sembra essere facile da trovare su Google.

risposta

23

Common Lisp è un linguaggio progettato per lo sviluppo di applicazioni grandi e complesse. Quello che negli anni '80 si pensava fosse una grande applicazione. Ma ha ottenuto da sistemi di produzione diverse strutture per gestire gli errori e persino un certo supporto per il controllo in fase di compilazione. Ancora un sacco di codice è scritto per software prototipo, sistemi di ricerca e/o scopi personali. Non trovi sempre un alto livello di qualità. Tieni anche presente che a volte un controllo molto severo può rendere un codice troppo rigido (ad esempio: molti client HTTP invieranno richieste non conformi, ma è così che è e non è possibile rifiutarli facilmente senza perdere un gran numero di possibili utenti). sguardo

Let alcuni esempi di come il Common Lisp aiuta a scrivere software robusto:

tipizzazione forte e il tipo di esecuzione controllando

ci aspettiamo che un normale sistema di Lisp farà controlli runtime per ogni operazione. Evitare i sistemi Lisp che non lo fanno.

Se si dispone di una funzione numerica:

(defun foo (n x) 
    .... 
    (bar ...)) 

(defun bar (a b) 
    (+ a b)) 

Se FOO non fa controlli di argomento, ci aspettiamo che alla fine l'operazione + controllerà gli argomenti.In fase di esecuzione si verificherà un errore e verrà eseguito un gestore errori che, per impostazione predefinita, chiamerà un debugger.

Pensateci: tutte le operazioni (la maggior parte) verranno verificate in fase di esecuzione. Tutti gli oggetti hanno un tag di tipo primitivo (intero, stringa, matrice, vettore di bit, carattere, flusso, ...) e in fase di esecuzione il tipo verrà eventualmente controllato.

ma ci aspettiamo più dal runtime Lisp:

  • matrice delimita controlli
  • tipo fessura assegni
  • consistenza mucchio in caso di errori
  • varie Protezioni contro nocivi come ridefinire funzioni standard, eliminazione del pacchetto Common Lisp, errori aritmetici, ecc.

Utilizzo di un sistema Lisp gambo che non fa controlli di tipo runtime è un enorme dolore. Ora, Common Lisp ci consente di dichiarare parti del codice che non eseguono controlli di runtime. Strategia migliore: trovare la più piccola quantità di codice dove può essere eseguita senza creare un rischio (vedere LOCALLY).

liste di argomenti

Common Lisp permette una certa lista di argomenti il ​​controllo in fase di compilazione. Usalo.

(defun foo (&key (n 1) (x 1.0)) 
    ...) 

Ora un compilatore tipico prenderà una chiamata come (foo :y 2 :x 2.0) con un errore: argomento sbagliato parola chiave :y.

Lascia che il compilatore verifichi che l'elenco degli argomenti abbia il giusto numero di argomenti e che vengano utilizzati gli argomenti della parola chiave corretta.

CLOS, il Lisp Object sistema comune

Usa CLOS.

(defmethod foo ((n integer) (x float)) ...) 

Se si definisce un metodo come sopra, in fase di esecuzione nel corpo metodo n sarà un numero intero e x sarà un galleggiante. Se chiami FOO con altri tipi di argomenti e nessun metodo, allora otteniamo un errore di runtime.

Simili per gli slot di esempio: è possibile dichiarare i tipi.

(defclass bar() 
    ((x :type float) 
    (n :type integer))) 

Utilizzare un'implementazione Common Lisp che controlli effettivamente tali dichiarazioni o scriva i propri assegni.

Inoltre: non creare strutture di dati non elaborate basate su elenchi. Inserirli sempre in classi e metodi CLOS. In questo modo si ottiene la giusta quantità di funzionalità di controllo e introspezione.

Controlla i tipi in fase di esecuzione

Common Lisp fornisce una macro per il tipo di runtime controllo: CHECK-TYPE.

(defun foo (n x) 
    (check-type n integer) 
    (check-type x float) 
    (* (isqrt n) (sqrt x))) 

Il CHECK-TYPE macro consente tipo di fantasia controllo e anche riparare l'errore.

CL-USER 27 > (foo 2000 5) 

Error: The value 5 of X is not of type FLOAT. 
    1 (continue) Supply a new value of X. 
    2 (abort) Return to level 0. 
    3 Return to top loop level 0. 

Type :b for backtrace or :c <option number> to proceed. 
Type :bug-form "<subject>" for a bug report template or :? for other options. 

CL-USER 28 : 1 > :c 1 

Enter a form to be evaluated: 5.0 

Si noti che è possibile utilizzare i tipi per specificare elementi come l'intervallo per numeri, dimensioni di matrice o simili.

Ad esempio questo controlla che un oggetto associato alla variabile a1 è una matrice bidimensionale di dimensioni 3 a 3:

(check-type a1 (array * (3 3))) 

noti che è possibile definire tipi con DEFTYPE con predicati tipo arbitrari.

Uso Lisp costruisce quali errori

Ad esempio ecase vs case segnale:

CL-USER 37 > (let ((code 10)) 
       (ecase code 
       (1 'fine))) 

Error: 10 fell through ECASE expression. 
Wanted one of (1). 

ecase segnala automaticamente un errore, quando nessuna clausola è la corrispondenza.

La macro ASSERT consente di verificare asserzioni arbitrarie.

Common Lisp fornisce una macro integrata ASSERT.

(defun foo (n x) 
    (assert (and (integerp n) (evenp n)) (n)) 
    (assert (floatp x) (x)) 
    (* (isqrt n) (sqrt x))) 

Anche in questo caso, certa quantità di riparazione di runtime è disponibile:

CL-USER 33 > (foo 2001 5.0) 

Error: The assertion (AND (INTEGERP N) (EVENP N)) failed. 
    1 (continue) Retry assertion with new value for N. 
    2 (abort) Return to level 0. 
    3 Return to top loop level 0. 

Type :b for backtrace or :c <option number> to proceed. 
Type :bug-form "<subject>" for a bug report template or :? for other options. 

CL-USER 34 : 1 > :c 1 
Enter a form to be evaluated: 
2000 
98.38699 

Usa CLOS per semplice Design by Contract

(defclass bar() 
    ((n :type integer) 
    (x :type float))) 

(defmethod setup-bar ((b bar) (n1 integer) (x1 float)) 
    (with-slots (n x) b 
     (setf n n1 x x1)) 
    b)) 

Ora possiamo scrivere un metodo in più per verificare la presenza di esempio che n è maggiore di x:

(defmethod setup-bar :before ((b bar) (n1 integer) (x1 float)) 
    (assert (> n x) (n x))) 

Il : prima metodo sarà sempre eseguito prima il metodo principale.

Aggiungi un Design by Contract sistema di CLOS

Non ci sono librerie per questo. Quid Pro Quo è un esempio. C'è anche un'implementazione DBC più semplice e precedente di Matthias Hölzl: Design by Contract.

gestione degli errori avanzata con la condizione del sistema

tipi di scrittura di condizione:

(define-condition mailer-incomplete-delivery-error 
      (mailer-error) 
    ((recipient-and-status-list :initarg :recipient-and-status-list 
        :reader mailer-error-recipient-and-status-list))) 

Sopra è una condizione nuova, in base alla condizione mailer-error. A runtime possiamo prendere un codice di risposta SMTP e segnalare tale condizione.

Scrivere gestori e riavvii per gestire gli errori. È avanzato. L'uso estensivo del sistema di condizioni di solito indica un codice migliore.

Scrivere e controllo test

In molti casi codice robusto ha bisogno di una suite di test. Common Lisp non fa eccezione.

Facciamo gli errori Segnala utente

In molti implementazione Common Lisp si può ottenere l'oggetto condizione di errore, un backtrace e alcuni dati ambientali. Scrivili in un log degli errori. Consenti all'utente di segnalarli. Ad esempio, LispWorks ha il comando :bug-form nel debugger.

+1

Solo per aggiungere al progetto per contratto, [quid-pro-quo] (https://github.com/sellout/quid-pro-quo) aggiunge a CLOS come * runtime * controlla – PuercoPop

+0

Una risposta fantastica, migliore di quanto avrei potuto sperare. –

+2

Potrei aggiungere: le asserzioni CHECK-TYPE aiutano anche il compilatore ad ottimizzare il codice. Molti compilatori CL sono "intelligenti" abbastanza da dedurre che, se 'x' ha passato' (check-type x (intero 0 10)) ', che possono quindi ottimizzare il codice successivo in quella funzione solo per gli interi da 0 a 10 Spesso, vinci due volte da queste dichiarazioni. – BRFennPocock