2015-07-01 14 views
25

"C++ Primer" (5a edizione) suggerisce a pagina 149 che un codice breve può essere meno soggetto a errori rispetto a un'alternativa più lunga. Si porta la seguente riga di codice per servire come esempio:Quali errori vengono salvati per brevità?

cout << *iter++ << endl; 

si conclude che la riga precedente è meno soggetto a errori rispetto all'alternativa:

cout << *iter << endl; 
++iter; 

Per me, anche se il significato di *iter++ è chiaro, questa espressione richiede ancora uno sforzo mentale non zero per analizzare e quindi la seconda versione è più leggibile. Quindi, voglio capire: cosa c'è di più soggetto a errori nella seconda versione?

+31

Qualcuno stava fumando qualcosa quando è stato scritto. Chiaramente il 2 non è ambiguo. – leppie

+2

Penso più codice. Scrivere più codice porta a più incline agli errori. –

+0

Non vedo questo codice nel libro. Sei sicuro della pagina? Quale capitolo/paragrafo è? – Brahim

risposta

4

Posso pensare alle linee che vengono riordinate per qualche motivo (forse sono state separate da qualche altro codice e successivamente è stato modificato), o un if/for/while che viene aggiunto, senza parentesi o con una posizione errata:

++iter; 
cout << *iter << endl; 

if (some_condition) 
cout << *iter << endl; 
++iter; 

while (something_happens) 
{ 
cout << *iter << endl; 
} 
++iter; 

In questo piccolo esempio, gli insetti sono abbastanza evidenti, ma che non può essere il caso quando si hanno molte più righe.

(E sì, so che l'indentazione nel 2 ° e 3 ° esempio dovrebbe essere corretta, ma purtroppo ho visto molti esempi come questi).

+0

Questi tipi di problemi possono essere risolti almeno altrettanto efficacemente in altri modi, incluso l'uso di funzioni (per contenere funzionalità specificate, e il posto dove guardare quando viene interrotta la funzionalità), e usando linee guida di progettazione e codifica attentamente selezionate (rientro, uso di blocchi, ecc.). – Peter

+1

Non ho mai detto il contrario :-) Ho solo cercato di rispondere alla domanda: "Qual è più una tendenza agli errori nella seconda versione?", Enumerando così le possibilità. –

21

Anche io non sono d'accordo con l'autore.

In genere ogni snippet di codice ha la tendenza a essere modificato in futuro.

Ad esempio questa istruzione

cout << *iter++ << endl; 

può essere modificata come segue

cout << *iter++ << ... << SomeFunction(*iter) << endl; 

In questo caso il codice avrà un comportamento indefinito perché l'ordine di valutazione degli argomenti delle funzioni non è specificato.

Quindi, a mio parere questo frammento di codice

cout << *iter << endl; 
++iter; 

è meno effetti collaterali soggetti a errori. :)

L'introduzione di un frammento di codice non lo rende meno soggetto a errori.

Inoltre nel frammento di codice è stato mostrato che non esiste alcuna relazione logica tra l'incremento dell'iteratore e l'emissione del suo valore.

cout << *iter++ << endl; 

Quindi non è chiaro perché l'iteratore viene incrementato in questa informativa.

Separare questa istruzione in due istruzioni rende lo snippet di codice più chiaro perché sembra che l'incremento sia correlato ad altre parti del codice.

La brevità rende il codice più espressivo. Ma non significa che rende necessariamente anche il codice meno incline agli errori :)

+1

Ha senso leggere "* iter ++" come "il valore successivo da iter" - invece di dividerlo in due parti "il valore corrente da iter" e "avanzamento iter". ("Successivo" indica il primo che non è stato ancora elaborato, non quello successivo alla posizione corrente) – immibis

+0

@immibis Di solito è il caso in cui il corpo del ciclo di chiusura è costituito da una dichiarazione o quando questa affermazione è l'ultima affermazione nel corpo. Tuttavia, in ogni caso, nulla impedisce di dividere questa affermazione in due dichiarazioni. E il codice sarà ancora più leggibile perché non dovrai investigare su questa affermazione per essere sicuro di non aver dimenticato di incrementare l'iteratore. :) –

+0

Perché non hai votato per chiudere? Se una risposta inizia con "Non sono d'accordo con [un'altra risposta]", la domanda chiaramente sta chiedendo opinioni ... –

3

Non potrei non essere d'accordo con l'autore di più.

L'espressione *iter++ fa due cose diverse a iter - lo dereferisce e lo incrementa. Sì, l'ordine è definito, ma comprendere i risultati di due azioni su una singola variabile in una singola espressione richiede intrinsecamente più forza mentale di qualcosa che ha un solo effetto su una singola variabile.

Uno dei modi più affidabili per aumentare gli errori da uno sviluppatore è quello di aumentare inutilmente gli intelletti necessarie per comprendere che cosa fa in realtà il loro codice.

La virtù di rompere gli effetti in due affermazioni distinte è che è più facile da capire.

L'altro fenomeno è che i programmatori ai quali viene insegnato di includere due effetti su una singola variabile in una singola istruzione hanno più probabilità di creare espressioni con ancora più effetti su una variabile povera. Oltre a rendere il loro codice ancora meno comprensibile, aumenta anche la probabilità di comportamento non definito (dal momento che qualsiasi espressione che modifica qualsiasi variabile più di una volta in una singola espressione ha un comportamento non definito).

+0

Penso che il punto che l'autore sta cercando di fare è illustrato nella risposta di @ JavierNoval. – Brian

+0

L'esempio e il ragionamento del libro tende anche a credere che se quell'esempio è "buono", allora in modo simile "Scrivi (* iter ++);" è buono. Quindi 'Write2 (* iter ++, * iter ++);' è allo stesso modo "buono". Ma questo terzo esempio è estremamente negativo. – franji1

+0

@staticx: Il fatto che non sia d'accordo con il punto che l'autore sta facendo non significa che non riesca a capirlo. – Peter

5

Il fatto è che, quando si ha la funzione di contenimento poche righe su codice, ognuno di essi viene trattato come un "passo" per raggiungere un certo obiettivo. Quando leggi questa funzione leggi ogni riga e pensa a cosa fa e perché. Nel tuo esempio,

cout << *iter << endl; 
++iter; 

logicamente potrebbe essere un passo: "quando l'iterazione sopra il contenitore, scrivere ogni elemento a cout". È perché quando si dimentica una di queste righe, l'intero passaggio non è corretto. Naturalmente, questo stesso esempio non è particolarmente bello, perché non è difficile trovare un codice, in cui le due linee sono due diversi passaggi logici. Tuttavia, suppongo che l'autore intende, che scrivendo

cout << *iter++ << endl; 

a proteggere se stessi da dimenticare una parte di una fase di logica così come si effettua un segnale per un lettore che questo è un passo logico.

+3

+1. Se questo è ciò che intendevano gli autori di "C++ Primer", allora è un peccato che non lo spiegassero. Questa sarebbe una lezione importante per il lettore. – AlwaysLearning

+1

Dal libro che fa riferimento a quel pezzo di codice: "È studiare esempi di tale codice finché i loro significati non sono immediatamente . La maggior parte dei programmi C++ usa espressioni succinte piuttosto che equivalenti verbali . Pertanto, i programmatori C++ devono essere confortevoli con tali usi.Inoltre, una volta che queste espressioni sono familiari, troverai meno soggette a errori. " – Brian

+3

Fondamentalmente significa "Scrivi codice non valido per comprendere il codice errato" – inf

2

Non è un buon esempio, perché non si dovrebbe usare mentre senza bretelle, ma potrebbe portare a errori come questo: In questo caso si avrebbe un ciclo infinito quando si aggiunge il tempo.

while (*iter) 
    cout << *iter << endl; 
    ++iter; 

Penso che sia un cattivo esempio! Molto meglio sarebbe un esempio, in cui si hanno più passaggi dipendenti. Quindi qualcuno copia alcuni passaggi in un'altra funzione, ma manca alcune linee, che non sembrano essere semanticamente necessarie.

+0

Potresti voler aggiungere delle parentesi, perché così facendo rimarrai bloccato in un ciclo infinito. Il punto e virgola implica che non stai usando Python. – leppie

+1

@lebbie è stato il punto di questa risposta. Brevity ti farebbe risparmiare, se tutto il codice è su una riga, sei ok anche senza parentesi. - Ma questo è un punto debole, dal momento che dovresti sempre usare le parentesi graffe. Ma è lo stesso con copia e incolla, con diverse linee potresti perdere qualcosa – Falco

+0

Scusate :) Mio male, non ho letto correttamente la risposta. – leppie

3

Potrebbe valere la pena confrontare con costrutti simili in altre lingue.

In python, per iterare su una sequenza, utilizziamo un generatore. C'è essenzialmente una sola operazione che puoi fare con un generatore: chiama next che ottiene un elemento e fa avanzare il generatore. L'iterazione, una volta eseguita manualmente, viene eseguita ripetutamente chiamando next per ottenere i termini della sequenza fino a quando non solleva StopIteration per segnalare la fine della sequenza.

In java, per iterare su una sequenza, noi uno Iterator. Esiste essenzialmente una sola operazione che puoi fare con un iteratore: chiama next che ottiene un elemento e fa avanzare l'iteratore. L'iterazione, una volta eseguita manualmente, viene eseguita ripetutamente chiamando next per ottenere i termini della sequenza fino a hasNext restituisce false per segnalare la fine della sequenza.

In C/C++ ... spesso vogliamo ottenere un elemento e avanzare attraverso la sequenza. È stato stabilito da molto tempo (AFAIK prima che esistesse C++) che questa operazione sia *p++.

L'unico motivo per cui stiamo prendendo in considerazione l'idea di suddividere questo in due passaggi: un passo per ottenere l'elemento corrente e l'altro per passare al termine successivo è un artefatto di un dettaglio di implementazione .

In una situazione in cui uno è veramente pensare a questi due passaggi come cose indipendenti e indipendenti, quindi sarebbe meglio tenerli come espressioni separate.

Ma è relativamente ben stabilito che questo è non come le persone stanno pensando - le persone stanno pensando in termini di "ottenere un elemento e avanzare l'iteratore". L'iterazione, una volta eseguita manualmente, viene eseguita invocando ripetutamente *iter++ per ottenere i termini della sequenza, fino a quando iter == end_iter restituisce true per indicare la fine della sequenza.

Quando si pensa in questo modo, suddividere il passaggio concettuale in due elementi sintatticamente separati (*iter e ++iter) è più incline agli errori che tenerlo come un singolo passaggio.

Problemi correlati