¿Son las asignaciones en la condición parte de los condicionales una mala práctica?

35

Supongamos que quiero escribir una función que concatene dos cadenas en C. La forma en que lo escribiría es:

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';

}

Sin embargo, K&R en su libro lo implementó de manera diferente, particularmente incluyendo tanto en la parte de condición del ciclo while como sea posible:

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

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

}

¿Qué camino se prefiere? ¿Se recomienda o desaconseja escribir código como lo hace K&R? Creo que mi versión sería más fácil de leer por otras personas.

Richard Smith
fuente
38
No olvide que K&R se publicó por primera vez en 1978. Desde entonces, ha habido un par de pequeños cambios en la forma en que codificamos.
corsiKa
28
La legibilidad era muy diferente en la época de los teleimpresores y los editores orientados a líneas. Mezclar todas esas cosas en una sola línea solía ser más legible.
user2357112 es compatible con Monica el
15
Estoy sorprendido de que tengan índices y comparaciones con '\ 0' en lugar de algo como while (*s++ = *t++); (Mi C está muy oxidada, ¿necesito padres allí para la precedencia del operador?) ¿K&R lanzó una nueva versión de su libro? Su libro original tenía un código extremadamente conciso e idiomático.
user949300
44
La legibilidad es algo muy personal, incluso en los días de los teletipos. Diferentes personas prefieren diferentes estilos. Gran parte de la instrucción de interferencia tuvo que ver con la generación de código. En esos días, algunos conjuntos de instrucciones (por ejemplo, Datos generales) podrían agrupar varias operaciones en una sola instrucción. Además, a principios de los años 80, existía el mito de que el uso de paréntesis generaba más instrucciones. Tuve que generar el ensamblador para demostrarle al revisor del código que era un mito.
taza el
10
Tenga en cuenta que los dos bloques de código no son equivalentes. El primer bloque de código no copiará la terminación '\0'de t(las whilesalidas primero). Esto dejará la scadena resultante sin terminar '\0'(a menos que la ubicación de la memoria ya esté puesta a cero). El segundo bloque de código hará la copia de la terminación '\0'antes de salir del whilebucle.
Makyen

Respuestas:

80

Siempre prefiera la claridad sobre la inteligencia. En antaño, el mejor programador era aquel cuyo código nadie podía entender. "No puedo entender su código, debe ser un genio" , dijeron. Hoy en día, el mejor programador es aquel cuyo código cualquiera puede entender. El tiempo de la computadora es más barato ahora que el tiempo del programador.

Cualquier tonto puede escribir código que una computadora pueda entender. Los buenos programadores escriben código que los humanos pueden entender. (M. Fowler)

Entonces, sin duda, iría por la opción A. Y esa es mi respuesta definitiva.

Tulains Córdova
fuente
8
Ideología muy concisa, pero el hecho es que no hay nada de malo en la asignación de condicionales. Es preferible salir temprano de un bucle o duplicar código antes y dentro de un bucle.
Miles Rout
26
@MilesRout Hay. Hay algo mal con cualquier código que tenga un efecto secundario donde no lo espere, es decir, pasar argumentos de función o evaluar condicionales. Ni siquiera mencionar que if (a=b)puede confundirse fácilmente con eso if (a==b).
Arthur Havlicek
12
@Luke: "Mi IDE puede limpiar X, por lo tanto, no es un problema" es bastante poco convincente. Si no es un problema, ¿por qué el IDE hace que sea tan fácil "arreglarlo"?
Kevin
66
@ArthurHavlicek Estoy de acuerdo con su punto general, pero el código con efectos secundarios en los condicionales no es tan infrecuente: while ((c = fgetc(file)) != EOF)como el primero que me viene a la mente.
Daniel Jour
3
+1 "Teniendo en cuenta que la depuración es el doble de difícil que escribir un programa en primer lugar, si eres tan inteligente como puedes serlo cuando lo escribes, ¿cómo lo vas a depurar?" BWKernighan
Christophe
32

La regla de oro, igual que en la respuesta de Tulains Córdova, es asegurarse de escribir un código inteligible. Pero no estoy de acuerdo con la conclusión. Esa regla de oro significa escribir código que pueda entender el programador típico que terminará manteniendo su código. Y usted es el mejor juez sobre quién es el programador típico que terminará manteniendo su código.

Para los programadores que no comenzaron con C, la primera versión es probablemente más fácil de entender, por razones que ya saben.

Para aquellos que crecieron con ese estilo de C, la segunda versión puede ser más fácil de entender: para ellos, es igualmente comprensible lo que hace el código, para ellos, deja menos preguntas de por qué está escrito de la manera en que está, y para ellos , menos espacio vertical significa que se puede mostrar más contexto en la pantalla.

Tendrás que confiar en tu buen sentido común. ¿Para qué audiencia desea que su código sea más fácil de entender? ¿Este código está escrito para una empresa? Entonces el público objetivo es probablemente los otros programadores de esa compañía. ¿Es este un proyecto de pasatiempo personal en el que nadie trabajará excepto usted? Entonces eres tu propio público objetivo. ¿Es este código que quieres compartir con otros? Entonces esos otros son su público objetivo. Elija la versión que coincida con esa audiencia. Desafortunadamente, no hay una sola forma preferida de alentar.

hvd
fuente
14

EDITAR: La línea s[i] = '\0';se agregó a la primera versión, corrigiéndola como se describe en la variante 1 a continuación, por lo que esto ya no se aplica a la versión actual del código de la pregunta.

La segunda versión tiene la ventaja distintiva de ser correcta , mientras que la primera no lo es: no termina la cadena objetivo correctamente.

La "asignación en condición" permite expresar el concepto de "copiar cada carácter antes de verificar el carácter nulo" de manera muy concisa y de una manera que hace que la optimización para el compilador sea algo más fácil, aunque muchos ingenieros de software en estos días encuentran que este estilo de código es menos legible . Si insiste en usar la primera versión, tendría que

  1. agregue la terminación nula después del final del segundo ciclo (agregando más código, pero puede argumentar que la legibilidad hace que valga la pena) o
  2. cambie el cuerpo del bucle a "primero asigne, luego verifique o guarde el carácter asignado, luego incremente los índices". Verificar la condición en el medio del ciclo significa salir del ciclo (reduciendo la claridad, mal visto por la mayoría de los puristas). Guardar el carácter asignado significaría introducir una variable temporal (reduciendo la claridad y la eficiencia). Ambos aniquilarían la ventaja en mi opinión.
hjhill
fuente
Lo correcto es mejor que lo legible y conciso.
user949300
5

Las respuestas de Tulains Córdova y hvd cubren bastante bien los aspectos de claridad / legibilidad. Permítanme incluir el alcance como otra razón a favor de las asignaciones en condiciones. Una variable declarada en la condición solo está disponible en el alcance de esa declaración. No puede usar esa variable después por accidente. El ciclo for ha estado haciendo esto por siglos. Y es lo suficientemente importante como para que el próximo C ++ 17 introduzca una sintaxis similar para if y switch :

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

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

No. Es un estilo C muy estándar y normal. Su ejemplo es malo, porque debería ser un bucle for, pero en general no hay nada de malo en

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

por ejemplo (o con while).

Miles Rout
fuente
77
Hay algo mal con eso; `! = NULL` y sus parientes en un condicional C son natter, solo para aplacar a los desarrolladores que no se sienten cómodos con el concepto de un valor verdadero o falso (o viceversa).
Jonathan Cast
1
@jcast No, es más explícito incluirlo != NULL.
Miles Rout
1
No, es más explícito decirlo (x != NULL) != 0. Después de todo, eso es lo que C realmente está comprobando, ¿verdad?
Jonathan Cast
@jcast No, no lo es. Comprobar si algo es desigual a falso no es cómo se escriben condicionales en ningún idioma.
Miles Rout
"Comprobar si algo es desigual a falso no es cómo se escriben condicionales en ningún idioma". Exactamente.
Jonathan Cast
2

En los días de K&R

  • 'C' era código de ensamblaje portátil
  • Fue utilizado por programadores que pensaron en código ensamblador
  • El compilador no hizo mucha optimización
  • La mayoría de las computadoras tenían "conjuntos de instrucciones complejas", por ejemplo, while ((s[i++]=t[j++]) != '\0')se asignarían a una instrucción en la mayoría de las CPU (espero que el VAC de diciembre)

Hay dias

  • La mayoría de las personas que leen el código C no son programadores de código de ensamblaje
  • Los compiladores de C hacen mucha optimización, por lo tanto, es probable que el código más simple de leer se traduzca al mismo código de máquina.

(Una nota sobre el uso de llaves siempre: el primer conjunto de código ocupa más espacio debido a que tiene algunos "innecesarios" {}, en mi experiencia, a menudo evitan que el código se haya fusionado mal del compilador y permiten errores con ubicaciones incorrectas ";" detectado por herramientas.)

Sin embargo, en los viejos tiempos la segunda versión del código habría leído. (¡Si lo hice bien!)

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

Incluso poder hacer esto es una muy mala idea. Se le conoce coloquialmente como "El último error del mundo", así:

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

Si bien es probable que no cometa un error que es tan grave, es muy fácil equivocarse accidentalmente y causar un error difícil de encontrar en su base de código. La mayoría de los compiladores modernos insertarán una advertencia para las asignaciones dentro de un condicional. Están allí por una razón, y harías bien en prestarles atención y simplemente evitar esta construcción.

Mason Wheeler
fuente
Antes de estas advertencias, escribiríamos CODE_RED = alertpara que se produjera un error de compilación.
Ian
44
@Ian Yoda condicionales que se llama. Difícil de leer son. Lamentablemente la necesidad para ellos es.
Mason Wheeler
Después de un breve período introductorio de "acostumbrarse", las condiciones de Yoda no son más difíciles de leer que las normales. A veces son más legibles . Por ejemplo, si tiene una secuencia de ifs / elseifs, tener la condición que se está probando a la izquierda para un mayor énfasis es una ligera mejora de la OMI.
user949300
2
@ user949300 Dos palabras: Síndrome de Estocolmo: P
Mason Wheeler
2

Ambos estilos están bien formados, son correctos y apropiados. Cuál es más apropiado depende en gran medida de las pautas de estilo de su empresa. Los IDE modernos facilitarán el uso de ambos estilos mediante el uso de la sintaxis en vivo que resalta explícitamente áreas que de otro modo podrían haberse convertido en una fuente de confusión.

Por ejemplo, Netbeans resalta la siguiente expresión :

if($a = someFunction())

por motivos de "asignación accidental".

ingrese la descripción de la imagen aquí

Para decirle explícitamente a Netbeans que "sí, realmente tenía la intención de hacer eso ...", la expresión puede estar entre paréntesis.

if(($a = someFunction()))

ingrese la descripción de la imagen aquí

Al final del día, todo se reduce a las pautas de estilo de la empresa y la disponibilidad de herramientas modernas para facilitar el proceso de desarrollo.

Luke A. Leber
fuente