¿Alguien / puede desafiar al tío Bob por su amor por eliminar los "aparatos inútiles"?

94

Odio hacer referencia al contenido de pago, pero este video muestra exactamente de lo que estoy hablando. Precisamente 12 minutos en Robert Martin mira esto:

Un poco de código

Y dice "Una de mis cosas favoritas para hacer es deshacerme de los frenos inútiles" mientras lo convierte en esto:

Más código

Hace mucho tiempo, en una educación muy lejana, me enseñaron a no hacer esto porque facilita la introducción de un error al agregar otra línea con sangría pensando que está controlada por el ifcuando no lo está.

Para ser justos con el tío Bob, está refactorizando un método largo de Java a pequeñas funciones que, estoy de acuerdo, son mucho más legibles. Cuando termina de cambiarlo, (22.18) se ve así:

Aún más código

Me pregunto si se supone que eso valida la eliminación de las llaves. Ya estoy familiarizado con las mejores prácticas . ¿Se puede desafiar al tío Bob en este punto? ¿El tío Bob ha defendido la idea?

naranja confitada
fuente
66
Estoy votando para cerrar esta pregunta como fuera de tema porque cualquier respuesta a esto será "no" o "sí, y aquí hay una cita".
Blrfl
35
Espere unos años, tal vez también eliminará el salto de línea inútil y "si (vea (algo)) diga (algo);" en realidad será una línea de código ;-)
Steve Jessop
8
@SteveJessop Es bueno saber que tengo años por delante. : D Sin la nueva línea, no hay riesgo de un error infame. Agregar o quitar llaves según sea necesario es una sobrecarga adicional, pero se ahorra mucho más al leer.
maaartinus
99
El tío Bob hace algunos buenos puntos para hacerlo en Clean Code, página 35. Si un ifbloque es solo una línea, no importa. Si necesita agregar más líneas, debería ser otra función que reduzca el ifbloque a una línea (la llamada a la función). Si te adhieres a eso, entonces los frenos simplemente no importan, en absoluto .
13
debería hacerlo return (page==null) ? "" : includePage(mode,page);si le gusta tanto hablar ... He pensado que el estilo sin corsé es genial hasta que comencé a desarrollar aplicaciones profesionalmente. Ruido difuso, posibles errores tipográficos, etc. Los aparatos ortopédicos, al estar allí todo el tiempo, le ahorran el tiempo y la sobrecarga que necesitaría para presentarlos más adelante.
vaxquis

Respuestas:

47

La legibilidad no es poca cosa.

Soy de una mente mixta cuando se trata de llaves que encierran un solo método. Personalmente los elimino para cosas como declaraciones de devolución de una sola línea, pero dejar esas llaves en realidad nos mordió muy duro en el último lugar donde trabajé. Alguien agregó una línea de código a una ifdeclaración sin agregar también las llaves necesarias, y debido a que era C, bloqueó el sistema sin previo aviso.

Nunca desafío a nadie que sea religioso acerca de usar siempre aparatos ortopédicos después de ese pequeño fiasco.

Por lo tanto, veo el beneficio de la legibilidad, pero soy muy consciente de los problemas que pueden surgir cuando se dejan esos frenos.

No me molestaría en tratar de encontrar un estudio o la opinión publicada de alguien. Todos tienen una (una opinión, es decir), y debido a que es un tema estilístico, una opinión es tan buena como cualquier otra. Piensa en el problema tú mismo, evalúa los pros y los contras, y toma tu propia decisión. Si la tienda para la que trabaja tiene un estándar de codificación que cubre este problema, solo siga eso.

Robert Harvey
fuente
77
Esto me mordió demasiado recientemente, cuando la sangría estaba apagada y era engañosa.
Andy
12
Porque era C sin GCC 6's-Wmisleading-indentation .
wchargin
2
@CandiedOrange: es el tío Bob quien desafía el dogma establecido.
Robert Harvey
1
@RobertHarvey ¿No lo dejé claro? Sí, él es un dogma desafiante. Él está desafiando MI dogma. Solo trato de ser un buen estudiante y al menos considerarlo. Pero el tío Bob es tan carismático que me hace preguntarme si estoy perdiendo objetividad. Así que estoy pidiendo ayuda para encontrar un antídoto antes de tomar el koolaid.
candied_orange
1
@CandiedOrange: Absolutamente es una cuestión de contexto. Los que simplemente aceptan ciegamente el dogma no consideran el contexto; son los que aún usan la regla del "solo retorno" en los idiomas administrados, mucho después de que la regla haya dejado de ser útil y se haya convertido en un impedimento.
Robert Harvey
133

Puede encontrar varias promociones publicadas o rechazos de estilos sin abrazaderas aquí o aquí o donde se pintan los cobertizos para bicicletas.

Al alejarse de los cobertizos para bicicletas, ¿ recuerda el gran error de OS X / iOS SSL de 2014?

if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
    goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
    goto fail;
    goto fail;
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
    goto fail;

Sí, "causado" por bloques sin llaves https://www.imperialviolet.org/2014/02/22/applebug.html

Las preferencias pueden depender del estilo del corsé. Si tuviera que escribir

if (suiteSetup)
{
    includePage(mode, suiteSetup);
}

Podría estar inclinado a ahorrar espacio también. Pero

if (suiteSetup) {
    includePage(mode, suiteSetup);
}

solo usa una línea "extra".


Sé que no preguntaste, pero si estoy trabajando solo, soy un hereje. Si me quito las llaves, prefiero

if (suiteSetup != null) includePage(mode, suiteSetup);

No tiene el mismo problema visual que el error de estilo SSL de iOS, pero guarda incluso más líneas "innecesarias" que el tío Bob;) Se lee bien si está acostumbrado y tiene un uso convencional en Ruby ( includePage(mode, suiteSetup) unless suiteSetup.nil?). Oh, bueno, solo sé que hay muchas opiniones.

Paul Draper
fuente
13
Además, si lo usa } else[if(...)] {, eso no agrega líneas adicionales adicionales, por lo que agrega solo una línea adicional en todo el conjunto.
Random832
12
Me alegro de que aparezca el error SSL de iOS. Desde entonces, me he encontrado usando frenillos en tales situaciones cada vez más.
Zach Lipton
44
Con respecto al error "goto fail", vale la pena señalar que otros aspectos del estilo de codificación del tío Bob también lo habrían evitado, lo más importante es su fuerte preferencia por métodos extremadamente cortos. En primer lugar, nunca habría tolerado un método de 79 líneas, y si el método se hubiera dividido en otros más pequeños (a) el conflicto de fusión que causó la duplicación probablemente nunca hubiera sucedido y (b) el control de flujo más simple en el método probablemente habría significado que se habrían generado advertencias del compilador para el código inalcanzable que debería haber evitado el problema.
Jules
16
"goto fail" no fue causado por bloques de una sola línea, fue causado por una programación descuidada, incluso una revisión de código más descuidada, y ni siquiera una vez que pasó por el código manualmente.
gnasher729
35
Meh, la falta de llaves no causó ese error. Los programadores atroces (y la falta de una revisión de código significativa, pruebas) lo hicieron. ¡Solucione el problema disparando a los responsables, no atando las manos del resto de nosotros!
Carreras de ligereza en órbita
62

El tío Bob tiene muchas capas de defensa contra tal error que no eran tan comunes cuando "siempre usa llaves" era la sabiduría predominante:

  1. Una fuerte preferencia personal por los condicionales de una sola línea, por lo que los de varias líneas se destacan y reciben un escrutinio adicional.
  2. Un editor que sobresale automáticamente cuando inserta una segunda línea.
  3. Un conjunto completo de pruebas unitarias que ejecuta constantemente.
  4. Un conjunto completo de pruebas de integración.
  5. Una revisión por pares realizada antes de fusionar su código.
  6. Una repetición de todas las pruebas por un servidor de integración continua.
  7. Pruebas realizadas por un departamento de calidad del producto.

Creo que si alguien publicara un desafío al tío Bob, tendría una muy buena refutación con los puntos anteriores. Este es uno de los errores más fáciles de detectar temprano.

Karl Bielefeldt
fuente
16
Nunca he temido los accidentes como temo a las cosas que fallan en silencio. Al menos sabes cuándo ocurre un choque. La parte posterior de mi cerebro se estremece cuando veo estas cosas. Hormigueo de la misma manera que cuando veo while(true);{break;}. Si realmente hay un contexto válido para esto, me llevará un tiempo superarlo.
candied_orange
99
¿Tenemos una forma automatizada de verificar que includePage()no es una macro mal escrita con más de una declaración?
Martin York
51
@LokiAstari Sí, se llama "Java no tiene macros".
svick
11
Todo lo anterior son más "formas de evitar que las caídas relacionadas con la política de" frenos inútiles "me lastimen" que "razones para usar la política de" frenos inútiles ". Los hechos que necesita (especialmente el n. ° 1) deben considerarse más una desventaja que una ventaja.
SJuan76
16
@Jules ¡Oh sí! TODOS los productos que he recibido o lanzado en el trabajo tienen una cobertura de prueba del 100% (como mínimo), son revisados ​​por pares (varias veces) y todos los negocios tienen departamentos de control de calidad de software. Si. Supongo que ha encontrado lo mismo en su carrera profesional, porque cada negocio tiene equipos de programadores en lugar de programadores individuales, y les dan tiempo más que suficiente para hacer todas las pruebas que les gustaría hacer. Sí, que describe todos los lugares de trabajo perfectamente. ¿Por qué debería preocuparnos de agregar algunos errores adicionales cuando estamos seguros de que nuestro software SIEMPRE se envía 100% libre de errores?
SJuan76
31

En su mayor parte, esta es una preferencia personal, sin embargo, hay algunas cosas a considerar.

Posibles errores

Si bien se puede argumentar que los errores causados por el olvido al complemento de los frenos son raros, por lo que he visto que ellos no ocurren de vez en cuando (no olvidar el famoso Goto IOS falla error). Así que creo que esto debería ser un factor al considerar el estilo de su código (algunas herramientas advierten sobre sangría engañosa , por lo que también depende de su cadena de herramientas) .

Válido Código (que se lee como que podría ser un error)

Incluso suponiendo que su proyecto no sufre de este tipo de errores, al leer el código es posible que vea algunos bloques de código que parecen que podrían ser errores - pero no es así, tomando algunas de sus ciclos mentales.

Comenzamos con:

if (foo)
    bar();

Un desarrollador agrega un comentario útil.

if (foo)
    // At this point we know foo is valid.
    bar();

Más tarde, un desarrollador se expande en él.

if (foo)
    // At this point we know foo is valid.
    // This never fails but is too slow even for debug, so keep disabled.
    // assert(is_valid(foo));
    bar();

O agrega un bloque anidado:

if (foo)
    while (i--) {
        bar(i);
        baz(i);
    }

O usa una macro:

if (foo)
    SOME_MACRO();

"... Dado que las macros pueden definir varias líneas de código, ¿la macro usa do {...} while (0)varias líneas? Debería porque está en nuestra guía de estilo, ¡pero es mejor que lo verifique por si acaso!"


Los ejemplos anteriores son todos códigos válidos, sin embargo, cuanto más contenido en el bloque de código, más necesita leer para asegurarse de que no haya errores.

Tal vez su estilo de código define que los bloques de varias líneas requieren una llave (no importa qué, incluso si no son código) , pero he visto este tipo de comentarios agregados en el código de producción. Cuando lo lees, hay una pequeña duda de que quien editó esas líneas por última vez se olvidó de agregar un paréntesis, a veces siento que la necesidad de verificar dos veces está funcionando según lo previsto (especialmente cuando se investiga un error en esta área del código) .

Ruido difuso

Una razón práctica para usar llaves para líneas simples es reducir el ruido diferencial .

Es decir, cambiando:

if (foo)
    bar();

A:

if (foo) {
    bar();
    baz();
}

... hace que la línea condicional se muestre en un diff como modificada, esto agrega una sobrecarga pequeña pero innecesaria.

  • las líneas se muestran como modificadas en las revisiones de código, si sus diferentes herramientas están basadas en palabras, puede ver fácilmente que solo cambió la llave, pero eso lleva más tiempo verificar si la línea no cambió en absoluto.
    Dicho esto, no todas las herramientas son compatibles con la diferenciación basada en palabras, diff (svn, git, hg ... etc.) se mostrará como si toda la línea cambiara, incluso con herramientas sofisticadas, a veces es posible que necesite mirar rápidamente sobre una línea simple basado en diff para ver qué cambió.
  • Las herramientas de anotación (como git blame) mostrarán la línea como modificada, haciendo que el seguimiento del origen de una línea sea más paso para encontrar el cambio real .

Ambos son pequeños y dependen de la cantidad de tiempo que dedique a revisar o rastrear el código que confirma las líneas de código cambiadas.

Un inconveniente más tangible de tener cambios de líneas adicionales en una diferencia, su mayor probabilidad de que los cambios en el código causen conflictos que se fusionen y deben resolverse manualmente .


Hay una excepción a esto, para las bases de código que tienen {en su propia línea, no es un problema.

El argumento de ruido diferencial no se cumple si escribe en este estilo:

if (foo)
{
    bar();
    baz();
}

Sin embargo, esta no es una convención tan común, por lo que se agrega principalmente a la respuesta para completar (no sugerir que los proyectos deberían usar este estilo) .

ideasman42
fuente
8
Me gusta el ruido de reducción reducido comentado que es un plus definido.
Martin York
2
¡Si solo todos los que están enojados con JSON tuvieran alguna consideración por el ruido diferencial! Te estoy mirando, regla de no última coma.
Roman Starkov
1
Huh, no había considerado el beneficio de ruido diferencial del {estilo de línea propia. Realmente odio ese estilo, y no me gusta trabajar en proyectos donde se requiere ese estilo. Tal vez con eso en mente, me resultará un poco menos frustrante tener que codificar de esa manera. La densidad del código es importante. Si puede ver todas las funciones en su monitor a la vez, puede asimilar todo más fácilmente. Me gusta dejar líneas en blanco entre bloques de código separados dentro de una función para facilitar la lectura (agrupar declaraciones relacionadas), y ser forzado a desperdiciar espacio en otros lugares debilita los descansos previstos.
Peter Cordes
1
@ peter-cordes, que tampoco es fanático del {estilo de línea propia, solo lo incluyó para completar las respuestas. En cuanto a confiar en el uso de macros do{}while(0), creo que el problema con esto es que puedes confiar en él casi todo el tiempo. El problema es que los accidentes ocurren, los errores pueden pasar por la revisión del código ... y se pueden introducir errores que no hubieran sido de otra manera.
ideasman42
2
Si estamos enumerando posibles errores, la capacidad de comentar líneas individuales es otra buena cosa. Estaba rastreando un error causado por alguien que comentaba ciegamente el cuerpo de una declaración if de una línea. La siguiente declaración fue ejecutada condicionalmente, en lugar de ejecutada incondicionalmente.
Eldritch Cheese
15

Hace años, me llevaron a depurar un código C. El error fue una locura difícil de encontrar, pero finalmente se redujo a una declaración como:

if (debug)
   foo (4);

Y resultó que la persona que lo había escrito se había definido foocomo una macro. Una macro con dos líneas de código. Y, por supuesto, solo la primera de esas dos líneas estaba sujeta a if. (Entonces la segunda línea se ejecutó incondicionalmente).

Esto puede ser absolutamente exclusivo de C y su preprocesador, que realiza sustituciones antes de la compilación, pero nunca lo he olvidado. Ese tipo de cosas te deja huella, y ¿por qué no jugar con seguridad, especialmente si usas una variedad de idiomas y cadenas de herramientas y no puedes estar seguro de que tales travesuras no sean posibles en otro lugar?

Ahora sangro y uso frenillos de manera diferente a los demás, aparentemente. Para una sola línea if, haría:

if (debug) { foo (4); }

por lo que no se necesitan líneas adicionales para incluir los corchetes.

Wayne
fuente
66
En ese caso, quien escribió esa macro es el culpable.
gnasher729
66
Escribir macros de preprocesador C correctamente puede no ser intuitivo, pero se puede hacer. Y debe hacerse, como señala gnasher729. Y su ejemplo es la razón por la cual cualquier macro que contiene más de una declaración debe incluir su cuerpo en un do { ... } while(0)bucle. Esto no solo asegurará que siempre se tratará correctamente mediante las if()declaraciones adjuntas , sino también que el punto y coma al final de la declaración se traga correctamente. Quien no conozca estas reglas simples, simplemente no debe escribir macros de preprocesador. Defender en el sitio de llamadas es el lugar equivocado para defender.
cmaster
18
@cmaster: Cierto, pero la forma en que las macros (u otras características del lenguaje) están escritas por otros está fuera de mi control. Cómo uso los frenos está bajo mi control. Definitivamente no estoy diciendo que esta sea una razón de hierro para incluir aparatos ortopédicos, pero es algo que puedo hacer para defenderme de los errores de otras personas, así que lo hago.
Wayne
@ gnasher729, ¿a quién le importa si es culpa de otra persona? Si está haciendo revisiones de código y siguiendo algunos estándares de código razonables, también podría ser culpa de su equipo. Y si llega a los usuarios, culparán a la organización que lanza software con errores.
ideasman42
1
Quien tenga macros es el culpable.
Neme
14

Al "tío Bob" se le permite tener su opinión, a usted se le permite tener su opinión. No hay necesidad de desafiarlo.

Si desea apelar a la autoridad, tome Chris Lattner. En Swift, si las declaraciones perdieron sus paréntesis, pero siempre vienen con llaves. Sin discusión, es parte del lenguaje. Entonces, si "Tío Bob" comienza a quitar llaves, el código deja de compilarse.

Revisar el código de otra persona y "deshacerse de los frenos inútiles" es un mal hábito. Solo causa trabajo adicional cuando el código necesita ser revisado, y cuando los conflictos se crean innecesariamente. ¿Quizás "Tío Bob" es un programador tan increíblemente bueno que no necesita revisiones de código? Perdí una semana de mi vida porque un programador superior cambió "if (p! = NULL)" a "if (! P)" sin una revisión de código, oculto en el peor lugar posible.

Este es principalmente un debate de estilo inofensivo. Las llaves tienen la ventaja de que puede insertar otra línea de código sin agregar llaves. Como una declaración de registro, o un comentario (si es seguido por un comentario seguido de una declaración es simplemente horrible). declaración en la misma línea como si tuviera la desventaja práctica de que tiene problemas con muchos depuradores. Pero haz lo que prefieras.

gnasher729
fuente
1
Supongo que se trata de "desafiar" porque UB (sic!) Presenta sus opiniones como hechos, al menos me sorprende cuando lo escucho.
vaxquis
3
Decir "Haz lo que prefieras" es en realidad estar de acuerdo con el tío Bob en esto porque eso es lo que él argumenta "no importa". En muchos idiomas esto no es un problema. Anteriormente había pensado en todos aquellos en los que es un problema que SIEMPRE deberías usar llaves y no había visto a nadie desafiar eso. Ahora, aquí está el tío Bob desafiándolo y me pregunto si se saldrá con la suya porque trabaja en métodos tan pequeños y altamente refactorizados o porque está lleno de eso. No lo había considerado inofensivo exactamente por el tipo de errores que @PaulDraper menciona en su respuesta.
candied_orange
Re siempre : el ruido sintáctico distrae y la línea "en blanco" adicional para la llave de cierre agrega un separador visual en el párrafo donde las líneas no deben separarse, lo que dificulta aún más la legibilidad. Esto es especialmente importante en pequeños bloques concisos que usted menciona.
JDługosz
9

Mis razones para no quitar llaves son:

  1. reducir la fatiga de decisión. Si siempre usa aparatos ortopédicos, nunca tendrá que decidir si los necesitará o no.

  2. reduzca el arrastre de desarrollo: incluso si se esfuerza por extraer eventualmente todas las líneas múltiples de lógica a métodos, tener que convertir un sin corsé si en un corsé si agregar lógica es un poco molesto de arrastre de desarrollo. Por lo tanto, existe el esfuerzo de eliminar las llaves, y el esfuerzo de agregarlas nuevamente cuando necesita más código. Diminuto, pero molesto.

Michael Johnston
fuente
0

Un joven compañero de trabajo ha dicho que los aparatos ortopédicos que consideramos redundantes y superfluos son de hecho útiles para él. No recuerdo exactamente por qué, pero si le permite escribir mejor código más rápido, esa es solo la razón para mantenerlos.

Fwiw, acordamos un compromiso de que ponerlos en una línea no hace que esas condiciones previas tan breves no me puedan leer / distraer. P.ej

if (!param) { throw invalid_argument (blahblah); }
if (n < 2) { return 0; }
// real code for function starts here...

También señalo que estas condiciones previas introductorias son a menudo declaraciones de flujo de control para el cuerpo (por ejemplo, a return), por lo que el miedo a agregar una segunda declaración que esté bajo la misma condición y olvidar escribir llaves es discutible. Una segunda declaración bajo la condición sería un código muerto de todos modos y no tendría sentido.

Sospecho que la fluidez en el problema de lectura es causada por el analizador cerebral de la persona que está conectado para tener los frenos como parte de la declaración condicional. Esa no es una representación precisa de la gramática de C ++, pero podría ser un efecto secundario de aprender primero otros lenguajes, donde ese es el caso.

JDługosz
fuente
1
El tío Bob probablemente piensa que escribe mejor código más rápido sin ellos.
djechlin
2
Al igual que yo, y otros con fluidez en C ++.
JDługosz
1
"si le permite escribir mejor código más rápido, eso es solo una razón para mantenerlos" ++ Tengo un hijo que dijo lo mismo, por lo tanto, los guardamos.
RubberDuck
... y esa es la razón por sí sola para hacerlo como el tío Bob quiere.
djechlin
-1

Después de ver ese video hace un momento, noté que eliminar las llaves hace que sea más fácil ver dónde el código aún debe ser refactorizado. Si necesita llaves, su código probablemente puede ser un poco más limpio.

Dicho esto, cuando estás en un equipo, la eliminación de los aparatos ortopédicos probablemente conducirá a un comportamiento inesperado algún día. Digo quedártelos, y te ahorrarás muchos dolores de cabeza cuando las cosas salgan mal.

Gijs Overvliet
fuente
esto no parece ofrecer nada sustancial sobre los puntos hechos y explicados en las 10 respuestas anteriores
mosquito
-3

Me enseñaron a no hacer esto porque facilita la introducción de un error al agregar otra línea con sangría pensando que está controlada por el if cuando no lo está.

Si su código está bien probado en la unidad , este tipo de "error" explotaría en la cara.

Limpiar el código evitando cualquier paréntesis inútil y feo es una práctica que sigo.

Mik378
fuente
99
Por supuesto, lo contrario también es cierto: si su código está libre de errores, no necesita ninguna prueba unitaria. La realidad es que vivimos en un mundo imperfecto donde a veces hay errores en el código y a veces errores en las pruebas. (En mi experiencia, estos últimos son en realidad más comunes). Entonces, aunque las pruebas ciertamente reducen el riesgo de errores, esto no es excusa para encontrar otras formas de aumentar el riesgo nuevamente.
ruakh
3
Para agregar al comentario de ruakh: no es posible probar un código al 100% de la unidad.
B 6овић
Ven a ver mi código;) Mientras practico TDD, siempre es posible mostrar este tipo de error muy rápidamente.
Mik378
@ Mik378 eso probablemente no sea cierto. El punto es que si usara aparatos ortopédicos, ni siquiera tendría que preocuparse por eso.
GoatInTheMachine