In che modo i compilatori devono segnalare errori e avvisi?


11

Non ho intenzione di scrivere un compilatore nel prossimo futuro; tuttavia, sono abbastanza interessato alle tecnologie di compilazione e al modo in cui queste cose potrebbero essere migliorate.

A partire dai linguaggi compilati, la maggior parte dei compilatori ha due livelli di errore: avvisi ed errori, il primo è il più delle volte cose non fatali che dovresti correggere e errori che indicano la maggior parte delle volte che è impossibile produrre machine- (o byte-) codice dall'input.

Tuttavia, questa è una definizione piuttosto debole. In alcune lingue come Java, alcuni avvisi sono semplicemente impossibili da eliminare senza usare la @SuppressWarningdirettiva. Inoltre, Java considera alcuni problemi non fatali come errori (ad esempio, il codice non raggiungibile in Java genera un errore per un motivo che vorrei sapere).

C # non ha gli stessi problemi, ma ne ha alcuni. Sembra che la compilazione si verifichi in più passaggi e un errore di passaggio impedirà l'esecuzione degli ulteriori passaggi. Per questo motivo, il conteggio degli errori che si ottiene quando la compilazione fallisce è spesso gravemente sottovalutato. In una corsa potrebbe dire che hai due errori, ma una volta risolti, potresti averne 26 nuovi.

Scavare in C e C ++ mostra semplicemente una cattiva combinazione delle debolezze diagnostiche della compilazione di Java e C # (anche se potrebbe essere più preciso dire che Java e C # si sono appena fatti strada con metà dei problemi ciascuno). Alcuni avvisi dovrebbero davvero essere errori (ad esempio quando non tutti i percorsi di codice restituiscono un valore) e comunque sono avvertimenti perché, suppongo, al momento in cui hanno scritto lo standard, la tecnologia del compilatore non era abbastanza buona da rendere questo tipo di controlli obbligatori. Allo stesso modo, i compilatori spesso controllano più di quanto lo standard dice, ma usano ancora il livello di errore di avviso "standard" per i risultati aggiuntivi. E spesso, i compilatori non segnalano immediatamente tutti gli errori che sono riusciti a trovare; potrebbero essere necessarie alcune compilazioni per sbarazzarsi di tutti. Per non parlare degli errori criptici che i compilatori C ++ amano sputare,

Ora aggiungendo che molti sistemi di compilazione sono configurabili per segnalare guasti quando i compilatori emettono avvisi, otteniamo solo uno strano mix: non tutti gli errori sono fatali ma alcuni avvisi dovrebbero; non tutti gli avvertimenti sono meritati, ma alcuni sono esplicitamente soppressi senza ulteriore menzione della loro esistenza; e a volte tutti gli avvisi diventano errori.

I linguaggi non compilati hanno ancora la loro parte di segnalazioni errate di errori. Gli errori Typos in Python non verranno segnalati fino a quando il codice non verrà effettivamente eseguito e non sarà mai possibile espellere più di un errore alla volta perché lo script smetterà di essere eseguito dopo averne incontrato uno.

PHP, dalla sua parte, ha un sacco di livelli di errore più o meno significativi ed eccezioni. Errori grossolani vengono segnalati uno alla volta, gli avvisi sono spesso così negativi che dovrebbero interrompere lo script (ma non per impostazione predefinita), gli avvisi mostrano spesso gravi problemi logici, alcuni errori non sono abbastanza gravi da interrompere lo script, ma comunque e, come al solito con PHP, ci sono alcune cose davvero strane laggiù (perché diavolo abbiamo bisogno di un livello di errore per errori fatali che non sono davvero fatali? E_RECOVERABLE_E_ERRORSto parlando con te).

Mi sembra che ogni singola implementazione della segnalazione degli errori del compilatore che mi viene in mente sia rotta. Il che è un vero peccato, dal momento che tutti i bravi programmatori insistono su quanto sia importante gestire correttamente gli errori e tuttavia non riescano a ottenere i propri strumenti per farlo.

Quale pensi che dovrebbe essere il modo giusto per segnalare errori del compilatore?


-1: "Le lingue non compilate hanno ancora la loro quota di errori di segnalazione" Soggettivo e argomentativo. Davvero inutile. È una domanda o un reclamo?
S. Lott

2
@ S. Lott Penso che tu sia un po 'al limite qui. Trovo di essere molto più difficile con i linguaggi compilati e non sembra disturbarti.
zneak,

@zneak: le altre affermazioni sono più vicine all'essere fattuali e più difficili da analizzare. Quell'affermazione si dimostrò più facilmente soggettiva e argomentativa.
S.Lott

1
@ S.Lott Sbaglio affermando che Python indica un errore alla volta?
zneak,

1
@ S.Lott Quindi le cose devono essere cambiate, perché l'ultima volta che ho provato, qualsiasi errore di sintassi causava l'interruzione di Python nel tentativo di "compilare" e un errore di nome generava un'eccezione e non controllava il resto della funzione (anche se questo ha lasciato spazio per la segnalazione di un errore per unità testabile). La mia affermazione soggettiva e argomentativa è stata un'introduzione a quello che credevo essere un fatto, ma se non è più vero andrò a modificare la mia domanda. Come funziona ora?
zneak,

Risposte:


6

La tua domanda non sembra realmente riguardare il modo in cui segnaliamo gli errori del compilatore, ma piuttosto la classificazione dei problemi e cosa fare al riguardo.

Se iniziamo supponendo, per il momento, che la dicotomia avvertenza / errore sia corretta, vediamo come possiamo costruirci sopra. Qualche idea:

  1. Diversi "livelli" di avviso. Molti compilatori in qualche modo implementano questo (ad esempio GCC ha molti switch per configurare esattamente ciò che avvertirà), ma ha bisogno di lavoro - ad esempio, riportare la gravità di un avviso segnalato e la possibilità di impostare "avvisi sono errori "solo per avvisi al di sopra di un livello di gravità specificato.

  2. Classificazione sana di errori e avvisi. Un errore deve essere segnalato solo se il codice non soddisfa le specifiche e quindi non può essere compilato. Le dichiarazioni non raggiungibili, sebbene probabilmente un errore di codifica, dovrebbero essere un avvertimento , non un errore: il codice è ancora "valido" e ci sono casi legittimi in cui si vorrebbe compilare con codice non raggiungibile (ad esempio modifiche rapide per il debug) .

Ora cose su cui non sono d'accordo con te:

  1. Fare uno sforzo extra per segnalare ogni problema. Se c'è un errore, questo interrompe la compilazione. La build è rotta. La build non funzionerà fino a quando l'errore non verrà corretto. Quindi, è meglio segnalare immediatamente questo errore, piuttosto che "continuare" per cercare di identificare tutto il resto "sbagliato" con il codice. Soprattutto quando molte di queste cose sono probabilmente dovute all'errore iniziale comunque.

  2. Il tuo esempio specifico di avvertimento che dovrebbe essere un errore. Sì, probabilmente è un errore del programmatore. No, non dovrebbe interrompere la build. Se so che l'input per la funzione è tale da restituire sempre un valore, dovrei essere in grado di eseguire la compilazione ed eseguire alcuni test senza dover aggiungere ulteriori controlli. Sì, dovrebbe essere un avvertimento. E a quel dannatamente severo. Ma non dovrebbe spezzare la build in sé e per sé, a meno che la compilazione con avvisi non sia un errore.

Pensieri?


Sono d'accordo con te, tranne per i punti in cui non siamo d'accordo (duh), quindi questo è +1 da parte mia. Penso che sia abbastanza facile fare in modo che ogni percorso del codice restituisca un valore o interrompa il programma, considerando quanto è grave quando cadi effettivamente nel caso di un comportamento indefinito.
zneak,

7

Un problema che hai sollevato era la segnalazione incompleta degli errori - ad esempio, la segnalazione di 2 errori e, quando li risolvi, ne ottieni un sacco di più.

Questo è (in gran parte) un compromesso da parte dello scrittore del compilatore. A seconda di quale errore che hai fatto, è molto facile per il compilatore per iniziare a fraintendere quello che fare avere abbastanza male che si inizia a segnalare errori che hanno ben poco a che fare con la realtà. Solo per esempio, considera un semplice errore di battitura in cui hai qualcosa di simile itn x;invece di int x;. A meno che tu non abbia fatto qualcos'altro che itnsignifichi qualcosa, questo verrà segnalato come errore. Va bene per quanto va, ma ora considera cosa accadrà dopo - il compilatore esamina un sacco di codice che tenta di utilizzare x come variabile. Dovrebbe A) fermarsi e permetterti di risolverlo, o B) vomitare 2000 errori error: "x": undeclared identifiero qualcosa in quell'ordine? Considera un'altra possibilità:

int main()[

Questo è un altro errore abbastanza evidente - ovviamente dovrebbe essere un {anziché un [. Il compilatore può dirti abbastanza facilmente quella parte - ma dovrebbe poi continuare a segnalare un errore per qualcosa come x=1;dire qualcosa del genere error: statement only allowed inside a function?

Si noti che si tratta di problemi anche abbastanza banali: quelli molto peggiori sono facili da trovare (specialmente, come molti di noi sanno, quando si entra nei modelli C ++). La linea di fondo è che lo scrittore del compilatore è solitamente bloccato nel tentativo di scendere a compromessi tra la segnalazione di errori falsi (ovvero la segnalazione di un errore, anche se va bene) e la mancata segnalazione di errori reali. Ci sono alcune regole empiriche che la maggior parte segue per cercare di evitare di sbagliare troppo in entrambe le direzioni, ma quasi nessuna di queste è quasi perfetta.

Un altro problema che hai menzionato era Java e @SupressWarning. Questo è abbastanza diverso da quanto sopra - sarebbe abbastanza banale da risolvere. L'unico motivo per cui non è stato risolto è che farlo non si adatta al "carattere" di base di Java, ovvero, secondo loro, "non è un bug, è una caratteristica". Anche se di solito è uno scherzo, in questo caso le persone coinvolte sono così fuorviate da credere davvero che sia vero.

Il problema che citi in C e C ++ con percorsi di codice che non restituiscono un valore non è proprio quello di consentire compilatori primitivi. Consentire decenni di codice esistente , alcuni dei quali nessuno vuole correggere, toccare o persino leggere. È antico e brutto ma funziona e nessuno vuole altro che continuare a lavorare. Nel bene e nel male, i comitati linguistici sono praticamente bloccati nel mantenere quella retrocompatibilità, quindi continuano a consentire cose che a nessuno piace davvero - ma alcune persone (almeno pensano di aver bisogno).


3
Oltre al tuo punto sui primi errori che causano molti altri, c'è anche il fatto che i passaggi successivi sono spesso costruiti per richiedere che i passaggi precedenti siano stati completati correttamente. Ad esempio, uno dei primi passaggi nel compilatore C # verifica che non vi siano cicli nel grafico dell'ereditarietà: non hai A ereditato da B che eredita da A. Se desideri continuare e generare un elenco di tutti gli errori successivi, ogni passaggio successivo dovrebbe essere in grado di far fronte ai cicli, rendendolo significativamente più lento anche su compilazioni "buone".
Anon.

@Anon. Il compilatore Java compie sforzi molto migliori per sopravvivere ai primi passi e non lo trovo significativamente più lento. Per me è alquanto fastidioso quanto velocemente si cscarrende.
zneak,

@zneak: come dice Jerry, è un compromesso da parte degli sviluppatori dei compilatori. Scrivere una buona diagnostica degli errori è in realtà un problema molto difficile (guarda clang per un esempio di quanto puoi davvero prenderlo). Vedi qui per una buona discussione delle fasi e dei passaggi del compilatore C #.
Dean Harding,
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.