Gli incarichi nella condizione dei condizionali sono una cattiva pratica?


35

Supponiamo di voler scrivere una funzione che concatena due stringhe in C. Il modo in cui la scriverei è:

void concat(char s[], char t[]){
    int i = 0;
    int j = 0;

    while (s[i] != '\0'){
        i++;
    }

    while (t[j] != '\0'){
        s[i] = t[j];
        i++;
        j++;
    }

    s[i] = '\0';

}

Tuttavia, K&R nel loro libro lo ha implementato in modo diverso, in particolare includendo quanto più possibile nella parte condizione del ciclo while:

void concat(char s[], char t[]){
    int i, j;
    i = j = 0;
    while (s[i] != '\0') i++;

    while ((s[i++]=t[j++]) != '\0');

}

Quale modo è preferito? È incoraggiato o scoraggiato scrivere codice come fa K&R? Credo che la mia versione sarebbe più facile da leggere da altre persone.


38
Non dimenticare, K&R è stato pubblicato per la prima volta nel 1978. Da allora ci sono stati un paio di piccoli cambiamenti nel modo in cui codifichiamo.
corsiKa

28
La leggibilità era molto diversa ai tempi delle teleprinter e degli editor orientati alla linea. Mushing tutta quella roba su una sola riga era più leggibile.
user2357112 supporta Monica il

15
Sono scioccato dal fatto che abbiano indici e confronti a '\ 0' anziché a qualcosa del tipo while (*s++ = *t++); (La mia C è molto arrugginita, ho bisogno di parentesi lì per la precedenza dell'operatore?) K&R ha rilasciato una nuova versione del suo libro? Il loro libro originale aveva un codice estremamente conciso e idiomatico.
user949300,

4
La leggibilità è una cosa molto personale, anche ai tempi dei teletipi. Persone diverse preferiscono stili diversi. Molte delle istruzioni relative allo spam hanno avuto a che fare con la generazione del codice. A quei tempi, alcune serie di istruzioni (ad es. Dati generali) potevano raggruppare diverse operazioni in un'unica istruzione. Inoltre, nei primi anni '80, c'era un mito secondo cui l'uso della parentesi generava più istruzioni. Ho dovuto generare l'assemblatore per dimostrare al revisore del codice che era un mito.
coppa

10
Si noti che i due blocchi di codice non sono equivalenti. Il primo blocco di codice non copierà la terminazione '\0'da t( whileprima esce). Ciò lascerà la sstringa risultante senza terminazione '\0'(a meno che la posizione della memoria non sia già stata azzerata). Il secondo blocco di codice eseguirà la copia della terminazione '\0'prima di uscire dal whileciclo.
Makyen,

Risposte:


80

Preferisci sempre la chiarezza rispetto all'intelligenza. In passato il miglior programmatore era quello il cui codice nessuno poteva capire. "Non riesco a dare un senso al suo codice, deve essere un genio" , hanno detto. Oggi il miglior programmatore è quello di cui chiunque può capire il codice. Il tempo del computer ora è più economico del tempo del programmatore.

Ogni pazzo può scrivere codice comprensibile a un computer. I bravi programmatori scrivono codice che gli umani possono capire. (M. Fowler)

Quindi, senza dubbio, sceglierei l'opzione A. E questa è la mia risposta definitiva.


8
Un'ideologia molto concisa, ma resta il fatto che non c'è nulla di sbagliato nell'assegnazione nei condizionali. È di gran lunga preferibile uscire presto da un loop o duplicare il codice prima e all'interno di un loop.
Miles Rout,

26
@MilesRout C'è. C'è qualcosa di sbagliato in qualsiasi codice che ha un effetto collaterale in cui non te lo aspetti, cioè passare argomenti di funzioni o valutare condizionali. Nemmeno menzionando ciò if (a=b)può essere facilmente confuso if (a==b).
Arthur Havlicek,

12
@Luke: "Il mio IDE può ripulire X, quindi non è un problema" non è abbastanza convincente. Se non è un problema, perché l'IDE semplifica la "correzione"?
Kevin,

6
@ArthurHavlicek Sono d'accordo con il tuo punto generale, ma il codice con effetti collaterali nei condizionali non è davvero così insolito: while ((c = fgetc(file)) != EOF)come il primo che mi viene in mente.
Daniel Jour,

3
+1 "Considerando che il debug è due volte più difficile della scrittura di un programma, in primo luogo, se sei intelligente quanto puoi esserlo quando lo scrivi, come lo eseguirai mai?" BWKernighan
Christophe

32

La regola d'oro, come nella risposta di Tulains Córdova, è assicurarsi di scrivere un codice comprensibile. Ma non sono d'accordo con la conclusione. Quella regola d'oro significa scrivere codice che il programmatore tipico che finirà per mantenere il codice può capire. E tu sei il miglior giudice su chi sia il tipico programmatore che finirà per mantenere il tuo codice.

Per i programmatori che non hanno iniziato con C, la prima versione è probabilmente più facile da capire, per ragioni che già conosci.

Per coloro che sono cresciuti con quello stile di C, la seconda versione potrebbe essere più facile da capire: per loro, è ugualmente comprensibile cosa fa il codice, per loro, lascia meno domande sul perché sia ​​scritto così com'è, e per loro , meno spazio verticale significa che è possibile visualizzare più contesto sullo schermo.

Dovrai fare affidamento sul tuo buon senso. A quale pubblico vuoi rendere il tuo codice più facile da capire? Questo codice è stato scritto per un'azienda? Quindi il pubblico target è probabilmente gli altri programmatori di quella compagnia. È un progetto di hobby personale su cui nessuno lavorerà se non te stesso? Quindi sei il tuo pubblico di destinazione. Questo codice vuoi condividere con gli altri? Quindi quegli altri sono il tuo pubblico di destinazione. Scegli la versione che corrisponde a quel pubblico. Sfortunatamente, non esiste un solo modo preferito per incoraggiare.


14

EDIT: la riga è s[i] = '\0';stata aggiunta alla prima versione, risolvendola così come descritto nella variante 1 di seguito, quindi questo non si applica più alla versione corrente del codice della domanda.

La seconda versione ha il vantaggio distintivo di essere corretta , mentre la prima non lo è - non termina correttamente la stringa di destinazione.

L '"assegnazione in condizione" consente di esprimere il concetto di "copia ogni carattere prima di controllare il carattere null" in modo molto conciso e in un modo che renda un po' più semplice l'ottimizzazione per il compilatore, sebbene molti ingegneri del software ritengano che questo stile di codice sia meno leggibile . Se insisti nell'usare la prima versione, dovresti farlo

  1. aggiungi la terminazione null dopo la fine del secondo ciclo (aggiungendo altro codice, ma puoi argomentare che la leggibilità lo rende utile) o
  2. cambia il corpo del loop in "prima assegna, poi controlla o salva il carattere assegnato, quindi incrementa gli indici". Controllare la condizione al centro del loop significa uscire dal loop (riducendo la chiarezza, disapprovata dalla maggior parte dei puristi). Salvare il carattere assegnato significherebbe introdurre una variabile temporanea (riducendo la chiarezza e l'efficienza). A mio avviso, entrambi annullerebbero il vantaggio.

Corretto è meglio che leggibile e conciso.
user949300,

5

Le risposte di Tulains Córdova e hvd coprono abbastanza bene gli aspetti di chiarezza / leggibilità. Consentitemi di esaminare l' ambito come un altro motivo a favore degli incarichi in condizioni. Una variabile dichiarata nella condizione è disponibile solo nell'ambito di tale istruzione. Non è possibile utilizzare quella variabile in seguito per caso. Il ciclo for lo fa da anni. Ed è abbastanza importante che il prossimo C ++ 17 introduca una sintassi simile per if e switch :

if (int foo = bar(); foo > 42) {
    do_stuff();
}

foo = 23;   // compiler error: foo is not in scope

3

No. È uno stile C molto standard e normale. Il tuo esempio è negativo, perché dovrebbe essere solo un ciclo for, ma in generale non c'è niente di sbagliato

if ((a = f()) != NULL)
    ...

per esempio (o con while).


7
C'è qualcosa di sbagliato in questo; `! = NULL` e i suoi parenti in un condizionale C sono più importanti, proprio lì per placare gli sviluppatori che non sono a proprio agio con il concetto di un valore che è vero o falso (o viceversa).
Jonathan Cast

1
@jcast No, è più esplicito da includere != NULL.
Miles Rout,

1
No, è più esplicito dire (x != NULL) != 0. Dopotutto, questo è ciò che C sta davvero controllando, giusto?
Jonathan Cast

@jcast No, non lo è. Controllare se qualcosa non è uguale a falso non è come si scrivono condizionali in qualsiasi lingua.
Miles Rout,

"Controllare se qualcosa non è uguale a falso non è il modo in cui scrivi i condizionali in qualsiasi lingua." Esattamente.
Jonathan Cast

2

Ai tempi di K&R

  • 'C' era un codice assembly portatile
  • Era usato da programmatori che pensavano nel codice assembly
  • Il compilatore non ha fatto molta ottimizzazione
  • La maggior parte dei computer aveva "serie di istruzioni complesse", ad esempio while ((s[i++]=t[j++]) != '\0')si associava a un'istruzione sulla maggior parte delle CPU (mi aspetto il Dec VAC)

Ci giorni

  • Molte persone che leggono il codice C non sono programmatori di codici assembly
  • I compilatori C eseguono molte ottimizzazioni, quindi è probabile che il codice più semplice da leggere venga tradotto nello stesso codice macchina.

(Una nota sull'uso sempre delle parentesi graffe: il primo set di codice occupa più spazio a causa di alcuni "non necessari" {}, nella mia esperienza questi spesso impediscono il codice che è stato malamente unito dal compilatore e consentono errori con posizionamenti errati ";" rilevato dagli strumenti.)

Tuttavia ai vecchi tempi la seconda versione del codice avrebbe letto. (Se ho capito bene!)

concat(char* s, char *t){      
    while (*s++);
    --s;
    while (*s++=*t++);
}

2

Anche riuscire a farlo è una pessima idea. È colloquialmente noto come "L'ultimo bug del mondo", in questo modo:

if (alert = CODE_RED)
{
   launch_nukes();
}

Mentre siete probabilmente non fare un errore che è piuttosto che grave, è molto facile da avvitare accidentalmente e causare un errore difficile da trovare nella vostra base di codice. La maggior parte dei compilatori moderni inserirà un avviso per le assegnazioni all'interno di un condizionale. Sono lì per un motivo, e faresti bene ad ascoltarli ed evitare questo costrutto.


Prima di questi avvisi, avremmo scritto in CODE_RED = alertmodo da dare un errore del compilatore.
Ian

4
@Ian Yoda Conditionals che viene chiamato. Sono difficili da leggere. Sfortunatamente la necessità per loro è.
Mason Wheeler,

Dopo un breve periodo introduttivo di "abituarsi", le condizioni di Yoda non sono più difficili da leggere di quelle normali. A volte sono più leggibili . Ad esempio, se si dispone di una sequenza di if / elseif, avere la condizione testata a sinistra per una maggiore enfasi è un leggero miglioramento dell'IMO.
user949300

2
@ user949300 Due parole: Sindrome di Stoccolma: P
Mason Wheeler

2

Entrambi gli stili sono ben formati, corretti e appropriati. Quale è più appropriato dipende in gran parte dalle linee guida di stile della tua azienda. Gli IDE moderni faciliteranno l'uso di entrambi gli stili attraverso l'uso di sfilacciamenti di sintassi live che evidenziano esplicitamente aree che altrimenti potrebbero essere diventate fonte di confusione.

Ad esempio, la seguente espressione è evidenziata da Netbeans :

if($a = someFunction())

per motivi di "assegnazione accidentale".

inserisci qui la descrizione dell'immagine

Per dire esplicitamente a Netbeans che "sì, intendevo davvero farlo ...", l'espressione può essere racchiusa tra parentesi.

if(($a = someFunction()))

inserisci qui la descrizione dell'immagine

Alla fine, tutto si riduce alle linee guida sullo stile aziendale e alla disponibilità di strumenti moderni per facilitare il processo di sviluppo.

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.