Lidiando con la sopa de llaves

11

He programado en C # y VB.NET durante años, pero principalmente en VB. Estoy haciendo un cambio de carrera hacia C # y, en general, me gusta más C #.

Sin embargo, un problema que tengo es la sopa de llaves. En VB, cada palabra clave de estructura tiene una palabra clave de cierre coincidente, por ejemplo:

Namespace ...
    Class ...
        Function ...
            For ...
                Using ...
                    If ...
                        ...
                    End If
                    If ...
                        ...
                    End If
                End Using
            Next
        End Function
    End Class
End Namespace

El mismo código escrito en C # termina siendo muy difícil de leer:

namespace ... {
    class ... {
        function ... {
            for ... {
                using ... {
                    if ... {
                        ...
                    }
                    if ... {
                        ...
                    }
                }
            }
            // wait... what level is this?
        }
    }
}

Al estar tan acostumbrado a VB, me pregunto si hay una técnica empleada por los programadores de estilo c para mejorar la legibilidad y garantizar que su código termine en el "bloque" correcto. El ejemplo anterior es relativamente fácil de leer, pero a veces al final de un fragmento de código tendré 8 o más niveles de llaves, lo que me obliga a desplazarme por varias páginas para descubrir qué llave termina el bloque que me interesa. en.

JDB todavía recuerda a Mónica
fuente
83
Sé que esto puede sonar como una predicación, y tal vez tengas condiciones especiales que lo requieran (porque sí, a veces es necesario, afortunadamente, esos momentos deberían ser raros), pero por lo general "... 8 o más niveles de llaves, lo que requiere que me desplace varias páginas para averiguar qué llave termina el bloque en el que estoy interesado " significa que el código necesita una refactorización y limpieza serias.
FrustratedWithFormsDesigner
77
Una cosa que he visto hacer, y he empezado a hacer, es que al final de una llave, agregaré un comentario al respecto. Algo así como // End's using X statement.
PiousVenom
14
@FrustratedWithFormsDesigner es acertado sobre el hecho de que su flujo de control es un desastre y necesita ser rehecho. Dicho esto, creo que te estás quejando más por las llaves que por el alcance y diría que solo necesitas acostumbrarte. Aprender un idioma con una sintaxis significativamente diferente a la que estás acostumbrado definitivamente parece una sopa por un tiempo, pero con la práctica desaparece. Solo necesita abrocharse el cinturón y tratar hasta que su cerebro comience a procesar la sintaxis de forma más natural, llegará allí.
Jimmy Hoffa
2
@FrustratedWithFormsDesigner: necesito limpiar muchos objetos COM en una situación de interoperabilidad COM. Estoy usando la técnica sugerida en este artículo: jake.ginnivan.net/vsto-com-interop . Esto crea fácilmente dos o tres capas. Cuando apilas el bucle for, la función, la clase y el espacio de nombres encima de eso (junto con una instrucción if), obtienes fácilmente varias capas de llaves.
JDB todavía recuerda a Mónica el
55
@TyrionLannister - Y esos pueden ser algunos de los comentarios más rápidos que no están sincronizados con lo que pertenecen ... Creo que si tuviera algo así, preferiría que se generara automáticamente (en la pantalla -tiempo solo, no persistente) por el IDE.
Clockwork-Muse el

Respuestas:

39

Coloque su llave inicial en el mismo "rango" que la final, de esta manera:

namespace ... 
{
    class ... 
    {
        function ... 
        {
            for ... 
            {
                using ... 
                {
                    if ... 
                    {
                        ...
                    }
                    if ... 
                    {
                        ...
                    }
                }
            }
            // It's the `function` level!
        }
    }
}
Robert Harvey
fuente
15
Estoy de acuerdo. Los corchetes egipcios me dan dolor de cabeza.
PiousVenom
8
Además, la mayoría de los IDE (probablemente) resaltan el compañero de un aparato ortopédico cuando hace clic en él.
StuperUser
3
@TyrionLannister: ¡Gracias por finalmente darme un término para ese estilo! Nunca tuve un buen nombre para ellos aparte de "llaves mal alineadas".
FrustratedWithFormsDesigner
3
@ Cyborgx37: ¿Su IDE tiene la función "Ir a paréntesis coincidentes"? Por lo general, está vinculado a un atajo de teclado que mueve automáticamente el cursor al corsé que coincide con el resaltado actual.
FrustratedWithFormsDesigner
3
Debo decir que no veo cómo esto lo hace más fácil. De cualquier manera, solo mira la pantalla en el nivel de sangría de la llave hasta llegar a una palabra clave. Y con todas esas líneas adicionales, ahora tiene que buscar más.
Blorgbeard sale el
14
  • Dependiendo de su IDE: coloque el cursor en la llave de apertura / cierre y resaltará tanto eso como la llave correspondiente.
  • Contraiga el bloque y le mostrará dónde se abre / cierra.
  • Escribe bloques de código más pequeños. Seriamente. Echa un vistazo Clean Codey nunca más te encuentres con este problema (y ten un código más legible / mantenible).

Una nota, la siguiente es una sintaxis válida de C # que podría ayudar a su situación particular:

using (var type = new MyDisposable1())
using (var type2 = new MyDisposable2())
{
    /* do what you will with type2 and type2 */
}
Steven Evers
fuente
La búsqueda del único personaje resaltado es lo que me llevó a publicar esta pregunta en primer lugar.
JDB todavía recuerda a Monica el
2
@ Cyborgx37: De ahí el punto 3. Si su bloque de código completo cabe en la pantalla, nunca tendrá que cazar. En la mayoría / todas las clases que escribo, los únicos pares de llaves que no caben en la pantalla son el espacio de nombres / clase.
Steven Evers
Lo que sugiere en los puntos 1 y 2 es lo que hago ahora ... pero esto requiere que deje el lugar donde estoy codificando y empiece a manipular mi vista del código para descubrir dónde poner la siguiente línea. El punto 3 está bien tomado, pero no siempre es posible, especialmente si su código requiere varias capas de usingbloques (ver jake.ginnivan.net/vsto-com-interop )
JDB todavía recuerda a Monica el
@ Cyborgx37: Vea mi edición.
Steven Evers
1
@ Cyborgx37 Si elige un color que se destaca de todo lo demás (usé un fondo morado y un texto blanco durante un tiempo, IIRC), entonces no hay necesidad de buscar el aparato ortopédico correspondiente: prácticamente le GRITA "¡Estoy AQUÍ ! ".
un CVn el
5

Una convención común es agregar un comentario después de la llave de cierre para indicar la estructura que se está cerrando:

if {
   ...
} // end if

while (condition) {
   ...
} // end while

Nunca me he acostumbrado a esta convención, pero a algunas personas les resulta útil.

John Bode
fuente
16
He visto a personas hacer esto, y cuando intercambian / cambian el comienzo del bloque (cambian de whilea a for, cambian a ifdeclaraciones) casi NUNCA recuerdan actualizar los comentarios finales, haciéndolos peor que inútiles. Esta convención probablemente solo será útil si puede obligarse a mantener los comentarios cada vez que {cambie la naturaleza de la coincidencia .
FrustratedWithFormsDesigner
Sí, he hecho algo de esto, pero es mucho trabajo extra (y ruido). Esperaba que hubiera algo más sencillo.
JDB todavía recuerda a Mónica el
55
Los comentarios solo deben explicar por qué nunca qué o cómo, ya que el código hace ambas cosas y confiar en los comentarios para explicar cualquiera de ellos significa que el código es difícil de leer, lo cual es una señal de que el código debe corregirse y no comentarse.
Jimmy Hoffa
1
Esto me hace preguntarme por qué nadie ha escrito un editor que muestre estos comentarios, pero en realidad no los incluye en el código ..
Brendan Long
2
@JimmyHoffa: Creo que una mejor regla para los comentarios es que deberían proporcionar claridad . Por lo general, eso significa responder "por qué", pero también puede significar otras cosas. No se deje atrapar por el dogma que le impide hacer cosas que realmente ayudan, como agregar ocasionalmente un comentario a una llave de cierre que está muy lejos de su llave de apertura.
Bryan Oakley
5

En general, cuando se hace difícil hacer coincidir las llaves en cualquier estilo, probablemente significa que el método es demasiado largo y debe ser refactorizado.

MaximR
fuente
5

Creo que necesitas resistirlo con los frenos. Eventualmente se convertirán en una segunda naturaleza para ti y te preguntarás cómo has vivido sin ellos.

Sin embargo, asegúrese de que estén sangrados adecuadamente y de que se sigan algunas convenciones de espaciado (no importa cuál).

Señor Fox
fuente
Un año después, y este consejo suena cierto. :)
JDB todavía recuerda a Mónica el
4

Elimino 2 niveles de anidamiento al contraer horizontalmente el espacio de nombres y los ámbitos de clase. Observe que los métodos están alineados con el borde izquierdo de la pantalla. No veo el punto de perder 2 niveles de sangría en cada archivo.

Después de eso, es raro que alguna vez anides más de 4 niveles de profundidad.

namespace FooNameSpace {
class Foo {

public void bar()
{
    while(true)
    {
        while(true)
        {
            break;
        }
    }
}

public void fooBar()
{
    foreach(var item in FooList)
    {
        foreach(var b in item.Bars)
        {
            if(b.IsReady)
            {
                bar();
            }
            bar();
        }
        bar();
    }
}

}}//end class, namespace
mike30
fuente
Me gusta esta idea, pero Visual Studio no parece
admitirla
@ Cyborgx37. Edito el texto externamente en VIM para que no sea un problema. Pero en Visual Studio haz: Control + A, luego presiona el botón "sangrar menos". Solo hazlo para archivos nuevos. No se moleste con los archivos existentes, ya que estropeará las comparaciones de diferencias en el control de origen.
mike30
Al escribir corchetes / llaves de cierre, se inicia un formateo automático por VS, pero siempre puede presionar CTRL + z para rechazar su sugerencia.
Alex en París el
1

Recientemente decidí intentar formalizar dos reglas sobre construcciones de control de flujo que básicamente son así:

  • No debería tener más que construcciones de flujo de código necesarias
  • Debe hacer que las construcciones de flujo de código sean lo más pequeñas posible

Por exactamente las razones que ha mencionado y de las que está al tanto, creo que estas son excelentes reglas a seguir. Hay un par de técnicas simples que puede emplear para lograrlas:

  • Salga del alcance lo antes posible (esto incluye el alcance de los bucles y las funciones)
  • Esté atento a los elses que podrían mitigarse al salir de la función del anterior si y aplique la técnica de salida del alcance como mencioné
  • Invierta sus verificaciones condicionales cuando el código dentro de un if sea mejor que el de afuera
  • Factoriza el código dentro de un bucle a otro método cuando el tamaño del bucle crece para oscurecer el resto del método
  • Esté atento a cualquier ámbito que solo contenga otro ámbito, por ejemplo, una función cuyo ámbito completo se llena con un if sin nada fuera del if

He detallado aquí ejemplos de cómo no seguirlos puede terminar con usted haciendo lo que dijo y colocando el código en el bloque de código incorrecto, lo cual es malo y una causa fácil de que surjan errores durante el mantenimiento.

Jimmy Hoffa
fuente
Gracias, puede haber un bloque o dos que podría refactorizar. Ayudará, pero es difícil refactorizar varios usingbloques anidados .
JDB todavía recuerda a Mónica el
@ Cyborgx37 en realidad usando bloques se puede alinear si están anidados cumpliendo con los alcances, mira aquí stackoverflow.com/questions/1329739/ ... básicamente funciona como construcciones de control de flujo de una sola línea cómo puedes hacerlo (true) doSomething (); también puede if (true) if (somethingElse) if (otherThings) {doThis (); Haz eso(); Haz lo que sea(); } y los ifs anidan como esperabas (NO ESCRIBAS CÓDIGO ASÍ POR EL AMOR DE DIOS, SOLO HAZ ESTO CON USOS Y NADA MÁS je je)
Jimmy Hoffa
Sí, sé sobre anidar usando bloques, pero esto solo funciona si tienes una sola línea debajo. En la mayoría de mi código, ese no es el caso. Todavía es una publicación muy útil en general ... aprendí algunas cosas (+1)
JDB todavía recuerda a Mónica el
@ Cyborgx37 sí, me doy cuenta de que la anidación del alcance solo funciona si no tienes bits adicionales, dicho eso, ¿hay un patrón en la forma de tus usos? ¿Sería posible mover algo de ese extra al constructor si está en la parte superior o eliminarlo si está en la parte inferior? Eso es si está modelado; Supongo que podría ser porque lo mencionó como un problema, por lo que probablemente se encuentre con estos utilizando nidos a menudo en su código.
Jimmy Hoffa
De hecho, he comenzado a trabajar en una implementación de patrón de fábrica que envuelve objetos COM en una clase "AutoCleanup". Luego uso una sola usingdeclaración en la clase de fábrica y, cuando se elimina, elimina automáticamente todas las clases envueltas. Está funcionando bien hasta ahora y ha reducido significativamente el número de usingdeclaraciones en mi código.
JDB todavía recuerda a Mónica el
1

Desafortunadamente, esta es una de las causas más antiguas de guerra en informática. Se pueden hacer argumentos razonables desde ambos lados (mejor economía de bienes raíces vertical versus capacidad más fácil de combinar visualmente la llave de apertura con la llave de cierre), pero en realidad un simple formateador de código fuente resolverá todo por usted. MS Visual C # tiene uno incorporado que funciona bien.

Sin embargo, tenga en cuenta que si está trabajando como parte de un equipo, se espera que cumpla con las convenciones utilizadas por ese equipo, por lo que vale la pena familiarizarse con ambos estilos y abstenerse de ser religioso sobre los estilos de aparatos ortopédicos.

Por lo tanto, mientras aprende por todos los medios, enfóquese en el estilo que le facilita aprender, pero vigile el otro mientras lo hace y le irá bien.

Maximus Minimus
fuente
1
No estoy seguro de si es el formateador predeterminado o algo en ReSharper, pero cuando solía usar C # teníamos un conjunto de opciones que reformateaba el código como parte del check-in. De esa forma, podría formatear el código como quisiera mientras trabajaba con él, pero sería reformateado a "estándar de proyecto" en el check-in. Creo que el único estándar de formato real que teníamos era usar espacios en lugar de pestañas.
TMN
1

Use Resharper, que le ayudará a recomendar formas de reducir la anidación. Además, lea el libro Clean Code de Bob Martin , que enfatiza que una función solo debe hacer una cosa y, por lo tanto, cada función solo debe tener media docena de líneas de largo, por lo que no tendrá que preocuparse por tantos niveles de anidamiento.

lorddev
fuente
1

Hay un complemento para el editor que puede ayudarlo en: C # Outline .

El complemento extiende el editor VS20xx para C # al agregar funciones para contraer, expandir y resaltar bloques de código anidados. Estas características permiten una edición y lectura más fáciles de contenidos anidados de bloques de código como if, while, etc.

Ninguna posibilidad
fuente
0

Si escribe su código en Visual Studio, también hay un complemento que le muestra puntos verticales entre el principio y el final de cada estructura que construya.

Pero, en general, creo que tomará algún tiempo hasta que te acostumbres a la "sopa de llaves". (Por cierto, me gusta mucho esa expresión. Suena un poco como un Nombre de Episodio para The Big Bang Theory)

mhr
fuente
0

La sangría te dice dónde estás, en ambos estilos de sintaxis. Si escribe un programa VB o un programa C # en una sola línea, pronto no podrá saber en qué parte de la sintaxis anidada se encuentra. La máquina analiza las frases finales de bloque o llaves, pero los humanos necesitan sangrado.

Las frases finales de bloque provienen de una era de tarjetas perforadas y cinta de papel, cuando la programación era mucho menos interactiva y visual. O, realmente, no es interactivo en absoluto. Era difícil ingresar a los programas, por lo que los programadores necesitaban compiladores para ser muy inteligentes sobre el análisis de sintaxis y la recuperación de errores.

En esa época pasada, el ciclo de edición-compilación-ejecución podría haber consistido en preparar tarjetas perforadas con un perforador de tarjetas, y luego alinearse en una ventana de envío de trabajo donde un empleado tomó las tarjetas perforadas y las envió a la máquina. Más tarde, el programador recolectaría la salida (impresa en papel) de otra ventana. Si el programa tuviera errores, la salida consistiría solo en el diagnóstico del compilador. Cuando los tiempos de respuesta son largos, el costo adicional de escribir en end iflugar de solo )se justifica si ayuda a mejorar la calidad de los diagnósticos, porque el programador necesita corregir tantos errores como sea posible en una sola iteración para reducir la cantidad de tiempo perdido iteraciones a través de la ventana de envío de trabajos.

Cuando falta una llave de cierre, es difícil saber qué llave abierta es la que no está cerrada. (Es posible que el compilador tenga que analizar la sangría para hacer una suposición informada). Si elimina una llave de cierre dentro de una función, parece que todo el resto del archivo es parte de esa función, lo que resulta en una ráfaga de mensajes de error inútiles. Mientras que si tiene una end functionsintaxis, el compilador puede deducir dónde termina la función errónea, recuperar y analizar las funciones subsiguientes correctamente, proporcionándole diagnósticos adicionales, si los hay, que sean significativos.

Cuando trabajas en un editor de texto con reconocimiento de código que sangra y colorea automáticamente tu código, en una pantalla de alta resolución donde puedes ver sesenta o más líneas, los argumentos para ese tipo de lenguajes torpes ya no se aplican. Puede editar y reconstruir progresivamente programas tan rápido que puede lidiar con un error a la vez. Además, al ver grandes secciones del programa simultáneamente en la pantalla y mantener una sangría adecuada, puede reducir la aparición de ese tipo de errores de anidación en primer lugar. Y un buen editor de texto de programación incluso marcará algunos tipos de errores de sintaxis mientras escribe. Además, hay editores plegables que colapsarán los bloques de un programa en función de su sintaxis, dando una vista "esquemática" de su estructura.

Lisp usó paréntesis desde el principio y quizás, no por casualidad, los piratas informáticos de Lisp fueron pioneros en la programación como una experiencia interactiva al construir sistemas que aceptaban programas en pequeños fragmentos (expresiones).

De hecho, no necesita símbolos finales, como lo ilustra el lenguaje Python. La ideación puede ser simplemente la estructura. Los humanos ya usan sangría para asimilar la estructura del código, incluso en idiomas en los que la máquina se basa en símbolos o frases finales.

Kaz
fuente
-1

Si está utilizando un IDE, simplemente presione Crtl+ k+ Dy el IDE hará el resto del trabajo.

Jagz W
fuente
qué eclipse IDE usa ctrl-shift-i para la sangría y ctrl-shift-f para el formateo
ratchet freak
check in visual studio
Jagz W