¿Cuál es el propósito de usar llaves (es decir, {}) para un if o loop de una sola línea?


321

Estoy leyendo algunas notas de mi profesor de C ++ y él escribió lo siguiente:

  1. Usar sangría // OK
  2. Nunca confíe en la precedencia del operador: utilice siempre paréntesis // OK
  3. Utilice siempre un bloque {}, incluso para una sola línea // no está bien , ¿por qué?
  4. Objeto Const en el lado izquierdo de la comparación // OK
  5. Use unsigned para variables que son> = 0 // buen truco
  6. Establezca el puntero en NULL después de la eliminación: doble protección de eliminación // no está mal

La tercera técnica no me resulta clara: ¿qué ganaría al colocar una línea en a { ... }?

Por ejemplo, tome este código extraño:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

y reemplazarlo con:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;

¿Cuál es el beneficio de usar la primera versión?


256
Legibilidad y mantenibilidad. No es inmediatamente obvio a qué bloque de instrucciones pertenece 'j ++', y que agregar código después de que no esté asociado con la instrucción if.
El bosque y los árboles

27
Siempre me dijeron que usara las llaves {} para estas líneas por un par de razones. Hace que el código sea más claro de leer. Además, es posible que otra persona dentro de seis meses necesite editar su código, por lo que la claridad es importante y con las llaves allí es menos probable que ocurra un error. No hay nada técnicamente más correcto al respecto, es más una cuestión de buenas prácticas. ¡Tenga en cuenta que un proyecto puede tener miles y miles de líneas de código para que un nuevo tipo lo atraviese!
RossC

30
No estoy de acuerdo con 6, ya que ocultará una doble eliminación y posiblemente ocultará errores lógicos.
Luchian Grigore

28
# 5 podría ser complicado - consideran este bucle: for (unsigned i = 100; i >= 0; --i).
Archie

36
Por cierto, (i % 2 == 0)contradice (2). Estás confiando en la precedencia del operador, y el significado es, por supuesto, ((i % 2) == 0)más que (i % (2 == 0)). Clasificaría la regla 2 como "un sentimiento válido pero 'siempre' está mal".
Steve Jessop

Respuestas:


509

Intentemos modificar también icuando incrementemos j:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
        i++;

¡Oh no! Viniendo de Python, esto se ve bien, pero de hecho no lo es, ya que es equivalente a:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;

Por supuesto, este es un error tonto, pero que incluso un programador experimentado podría cometer.

Otra muy buena razón se señala en la respuesta de ta.speot.is .

Un tercero en el que puedo pensar es el anidado if:

if (cond1)
   if (cond2) 
      doSomething();

Ahora, suponga que ahora quiere hacerlo doSomethingElse()cuando cond1no se cumple (nueva función). Entonces:

if (cond1)
   if (cond2) 
      doSomething();
else
   doSomethingElse();

lo cual obviamente está mal, ya que se elseasocia con lo interno if.


Editar: Dado que esto está recibiendo algo de atención, aclararé mi punto de vista. La pregunta que estaba respondiendo es:

¿Cuál es el beneficio de usar la primera versión?

Lo que he descrito. Hay algunos beneficios. Pero, en mi opinión, las reglas "siempre" no siempre se aplican. Entonces no apoyo totalmente

Utilice siempre un bloque {}, incluso para una sola línea // no está bien, ¿por qué?

No digo que siempre use un {}bloque. Si es una condición y comportamiento lo suficientemente simple, no lo haga. Si sospecha que alguien podría venir más tarde y cambiar su código para agregar funcionalidad, hágalo.


55
@Science_Fiction: Verdadero, pero si agrega i++ antes j++ , ambas variables seguirán estando dentro del alcance cuando se usen.
Mike Seymour

26
Esto suena muy razonable, pero descuida el hecho de que el editor hace la sangría, no usted, y sangrará i++;de una manera que muestre de inmediato que no es parte del bucle. (En el pasado, esto podría haber sido un argumento razonable, y he visto tales problemas. Hace unos 20 años. No desde entonces.)
James Kanze

43
@ James: eso no es un "hecho", sin embargo, es su flujo de trabajo. Y el flujo de trabajo de muchas personas, pero no de todos. No creo que sea necesariamente un error tratar la fuente C ++ como un archivo de texto sin formato, en lugar de la salida de un editor WYSIWYG (vi / emacs / Visual Studio) que impone reglas de formato. Entonces, esta regla es independiente del editor más allá de lo que necesita, pero no más allá de lo que la gente realmente usa para editar C ++. Por lo tanto "defensivo".
Steve Jessop

31
@JamesKanze ¿Realmente confías en la suposición de que todos siempre trabajan en IDE potentes? El último CI escrito fue en Nano. Incluso teniendo en cuenta eso, una de las primeras cosas que tiendo a apagar en un IDE es la sangría automática, porque el IDE tiende a interferir con mi flujo de trabajo no lineal , tratando de corregir mis 'errores' en base a información incompleta . Los IDE no son muy buenos para auto-sangrar el flujo natural de cada programador. Los programadores que usan tales características tienden a combinar su estilo con su IDE, lo cual está bien si solo usa un IDE pero no tanto si trabaja en muchos.
Rushyo

10
"Este es un error tonto, pero uno que incluso un programador experimentado podría cometer". - Como dije en mi respuesta, no lo creo. Creo que es un caso totalmente artificial que no plantea un problema en la realidad.
Konrad Rudolph

324

Es muy fácil cambiar accidentalmente el flujo de control con comentarios si no usa {y }. Por ejemplo:

if (condition)
  do_something();
else
  do_something_else();

must_always_do_this();

Si comenta do_something_else()con un comentario de una sola línea, terminará con esto:

if (condition)
  do_something();
else
  //do_something_else();

must_always_do_this();

Se compila, pero must_always_do_this()no siempre se llama.

Tuvimos este problema en nuestra base de código, donde alguien había entrado para deshabilitar algunas funciones muy rápidamente antes del lanzamiento. Afortunadamente lo atrapamos en la revisión de código.


3
Ohh chico !! es un comportamiento definido que must_always_do_this();se ejecutará si comentas // do_something_else ();
Mr.Anubis

1
@Supr, como se escribió por primera vez, dice que es difícil romper el flujo correcto si usa llaves, y luego da un ejemplo de lo fácil que es romper sin tener el código correctamente entre paréntesis
SeanC

20
Me encontré con esto el otro día. if(debug) \n //print(info);. Básicamente sacó toda una biblioteca.
Kevin

44
Fortunately we caught it in code review.¡Ay! Eso suena muy mal. Fortunately we caught it in unit tests.seria mucho mejor!
Bћовић

44
@ BЈовић Pero, ¿y si el código estuviera en una prueba unitaria? La mente se aturde. (Es broma, es una aplicación de legado No hay pruebas de unidad..)
ta.speot.is

59

Tengo mis dudas sobre la competencia del profesor. Considerando sus puntos:

  1. Okay
  2. ¿Alguien realmente escribiría (o querría leer) (b*b) - ((4*a)*c) ? Algunas precedentes son obvias (o deberían ser), y los paréntesis adicionales solo aumentan la confusión. (Por otro lado, debe usar los paréntesis en casos menos obvios, incluso si sabe que no son necesarios).
  3. Algo así como. Existen dos convenciones amplias para formatear condicionales y bucles:
    si (cond) {
        código;
    }
    
    y:
    si (cond)
    {
        código;
    }
    
    En el primero, estaría de acuerdo con él. La apertura {no es tan visible, por lo que es mejor asumir que siempre está ahí. En el segundo, sin embargo, yo (y la mayoría de las personas con las que he trabajado) no tengo problemas para omitir las llaves para una sola declaración. (Siempre, por supuesto, que la sangría es sistemática y que usa este estilo de manera consistente. (Y muchos programadores muy buenos, que escriben código muy legible, omiten las llaves incluso cuando se formatea de la primera manera).
  4. NO . Cosas como if ( NULL == ptr )son lo suficientemente feas como para dificultar la legibilidad. Escribe las comparaciones intuitivamente. (Lo que en muchos casos da como resultado la constante a la derecha). Su 4 es un mal consejo; todo lo que hace que el código no sea natural lo hace menos legible.
  5. NO . Cualquier cosa menos intestá reservada para casos especiales. Para programadores con experiencia en C y C ++, el uso de unsignedoperadores de bits de señales. C ++ no tiene un tipo cardinal real (o cualquier otro tipo de subrango efectivo); unsignedno funciona para valores numéricos, debido a las reglas de promoción. Presumiblemente, los valores numéricos en los que ninguna operación aritmética tendría sentido, como los números de serie, podrían serlo unsigned. Sin embargo, argumentaría en contra porque envía un mensaje incorrecto: las operaciones bit a bit tampoco tienen sentido. La regla básica es que los tipos integrales son int, a menos que exista una razón importante para usar otro tipo.
  6. NO . Hacer esto sistemáticamente es engañoso, y en realidad no protege contra nada. En estricto código OO, delete this;es a menudo el caso más frecuente (y no se puede establecer thisa NULL), y por otra parte, la mayoría deletese encuentran en los destructores, por lo que no puede acceder el puntero más adelante de todos modos. Y configurarlo NULLno hace nada con respecto a ningún otro puntero flotando. Configurar el puntero sistemáticamente para NULLdar una falsa sensación de seguridad, y realmente no te compra nada.

Mire el código en cualquiera de las referencias típicas. Stroustrup viola todas las reglas que ha dado, excepto la primera, por ejemplo.

Te sugiero que encuentres otro profesor. Alguien que realmente sabe de lo que está hablando.


13
El número 4 puede ser feo, sin embargo, tiene un propósito. Está tratando de prevenir if (ptr = NULL). Creo que nunca lo he usado delete this, ¿es más común de lo que he visto? No tiendo a pensar que establecer un puntero en NULL después del uso sea algo tan malo que hacer, pero YMMV. Tal vez soy solo yo, pero la mayoría de sus pautas no parecen tan malas.
Firedragon

16
@Firedragon: la mayoría de los compiladores avisarán a if (ptr = NULL)menos que lo escriba como if ((ptr = NULL)). Tengo que estar de acuerdo con James Kanze en que la fealdad de tener NULLprimero lo convierte en un NO definitivo para mí.
Leo

34
@JamesKanze: Debo decir que no estoy de acuerdo con la mayoría de lo que has dicho aquí, aunque aprecio y respeto tus argumentos para llegar a ellos. Para programadores con experiencia en C y C ++, el uso de operadores de bits de señales sin signo. - No estoy de acuerdo en absoluto: el uso de operadores de bits indica el uso de operadores de bits. Para mí, el uso de unsignedindica una aspiración por parte del programador de que la variable debe representar solo números positivos. La mezcla con números con signo generalmente generará una advertencia del compilador, que probablemente era lo que el profesor tenía la intención.
Componente 10 de

18
Para programadores experimentados de C y C ++, el uso de operadores de bits de señales sin firmar o no. size_talguien?
ta.speot.is

16
@ James Kanze, considere el propósito. Está comparando el código producido por un programador experimentado con ejemplos instructivos. El profesor proporciona estas reglas porque son el tipo de errores que ve que cometen sus alumnos. Con experiencia, los estudiantes pueden relajarse o ignorar estos absolutos.
Joshua Shane Liberman

46

Todas las otras respuestas defienden la regla 3 de su profesor.

Permítanme decir que estoy de acuerdo con ustedes: la regla es redundante y no lo recomendaría. Es cierto que teóricamente evita errores si siempre agrega llaves. Por otro lado, nunca me he encontrado con este problema en la vida real : al contrario de lo que implican otras respuestas, no me he olvidado de agregar las llaves una vez que se hicieron necesarias. Si usa la sangría adecuada, se vuelve inmediatamente obvio que necesita agregar llaves una vez que se sangra más de una declaración.

La respuesta del "Componente 10" en realidad resalta el único caso concebible en el que esto realmente podría conducir a un error. Pero, por otro lado, reemplazar el código mediante una expresión regular siempre garantiza un cuidado enorme de todos modos.

Ahora echemos un vistazo al otro lado de la medalla: ¿hay alguna desventaja en usar siempre llaves? Las otras respuestas simplemente ignoran este punto. Pero no es una desventaja: se necesita una gran cantidad de espacio vertical de la pantalla, y esto a su vez puede hacer que el código ilegible porque significa que usted tiene que desplazarse más de lo necesario.

Considere una función con muchas cláusulas de protección al principio (y sí, lo siguiente es un código C ++ incorrecto, pero en otros lenguajes esta sería una situación bastante común):

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
    {
        throw null_ptr_error("a");
    }
    if (b == nullptr)
    {
        throw null_ptr_error("b");
    }
    if (a == b)
    {
        throw logic_error("Cannot do method on identical objects");
    }
    if (not a->precondition_met())
    {
        throw logic_error("Precondition for a not met");
    }

    a->do_something_with(b);
}

Este es un código horrible, y sostengo firmemente que lo siguiente es mucho más legible:

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
        throw null_ptr_error("a");
    if (b == nullptr)
        throw null_ptr_error("b");
    if (a == b)
        throw logic_error("Cannot do method on identical objects");
    if (not a->precondition_met())
        throw logic_error("Precondition for a not met");

    a->do_something_with(b);
}

Del mismo modo, los bucles anidados cortos se benefician al omitir las llaves:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
        for (auto j = 0; j < a.h(); ++j)
            c(i, j) = a(i, j) + b(i, j);

    return c;
}

Comparar con:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
    {
        for (auto j = 0; j < a.h(); ++j)
        {
            c(i, j) = a(i, j) + b(i, j);
        }
    }

    return c;
}

El primer código es conciso; El segundo código está hinchado.

Y sí, esto puede mitigarse hasta cierto punto poniendo la llave de apertura en la línea anterior. Pero eso aún sería menos legible que el código sin ningún paréntesis.

En resumen: no escriba código innecesario que ocupe espacio en la pantalla.


26
Si usted no cree en la escritura de código que ocupa espacio en la pantalla sin necesidad, entonces usted tiene nada que poner la llave de apertura en su propia línea. Probablemente ahora voy a tener que agacharme y huir de la sagrada venganza de GNU, pero en serio, o quieres que tu código sea verticalmente compacto o no. Y si lo hace, no haga cosas diseñadas únicamente para hacer que su código sea menos compacto verticalmente. Pero como dices, después de haber solucionado eso, también querrás eliminar las llaves redundantes. O tal vez solo escriba if (a == nullptr) { throw null_ptr_error("a"); }como una línea.
Steve Jessop

66
@Steve Como cuestión de hecho, no puso la llave de apertura en la línea anterior, por la misma razón que declaró. Usé el otro estilo aquí para hacer más obvio cuán extrema puede ser la diferencia.
Konrad Rudolph

99
+1 Estoy completamente de acuerdo en que su primer ejemplo es mucho más fácil de leer sin llaves. En el segundo ejemplo, mi estilo de codificación personal es usar llaves en el for-loop externo y no en el interno. No estoy de acuerdo con @SteveJessop sobre tener que ser un extremo o el otro sobre el código verticalmente compacto. Omito llaves adicionales con líneas simples para reducir el espacio vertical, pero pongo mis llaves de apertura en una nueva línea porque me resulta más fácil ver el alcance cuando las llaves están alineadas. El objetivo es la legibilidad, y a veces eso significa usar más espacio vertical, otras veces significa usar menos.
Travesty3

22
"Nunca me he encontrado con este problema en la vida real": suerte. Cosas como esta no solo te queman, sino que te dan 90% de quemaduras de tercer grado (y eso son solo algunas capas de administración que exigen una solución a última hora de la noche).
Richard

99
@ Richard Simplemente no compro eso. Como he explicado en el chat, incluso si este error alguna vez ocurriera (lo cual me parece poco probable) es trivial solucionarlo una vez que miras el seguimiento de la pila porque es obvio dónde está el error solo mirando el código. Su reclamo exagerado es completamente infundado.
Konrad Rudolph

40

La base de código en la que estoy trabajando está dispersa con código por personas con aversión patológica a los aparatos ortopédicos, y para las personas que vienen más tarde, realmente puede hacer una diferencia en la mantenibilidad.

El ejemplo problemático más frecuente que he encontrado es este:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
    this_looks_like_a_then-statement_but_isn't;

Entonces, cuando llego y deseo agregar una declaración de entonces, puedo terminar fácilmente con esto si no tengo cuidado:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
{
    this_looks_like_a_then-statement_but_isn't;
    i_want_this_to_be_a_then-statement_but_it's_not;
}

Teniendo en cuenta que se tarda ~ 1 segundo para agregar las llaves y se puede ahorrar por lo menos unos minutos confusas depuración, ¿por qué alguna vez no ir con la opción reducido ambigüedad? Me parece una falsa economía.


16
¿No es el problema en este ejemplo en la indentación indebida y las líneas demasiado largas en lugar de las llaves?
Honza Brabec

77
Sí, pero seguir pautas de diseño / codificación que solo son "seguras" suponiendo que las personas también sigan otras pautas (como no tener líneas demasiado largas) parece estar planteando problemas. Si las llaves se hubieran colocado desde el principio, sería imposible terminar con un bloque de bloqueo incorrecto en esta situación.
mermelada

16
¿Cómo agregaría llaves if(really long...editor){ do_foo;}? debido a las dos líneas adicionales en el código.
Grizzly

1
Buen punto: estaba asumiendo que el uso forzado de los aparatos ortopédicos también los pondría en un lugar sensible, pero, por supuesto, alguien decidido a dificultar las cosas podría ponerlos en línea como en su ejemplo. Sin embargo, me imagino que la mayoría de la gente no lo haría.
mermelada

2
Lo primero y lo último que hago al tocar un archivo es presionar el botón de formateo automático. Elimina la mayoría de estos problemas.
Jonathan Allen

20

Mi 2c:

Usar sangría

Obviamente

Nunca confíe en la precedencia del operador: use siempre paréntesis

No usaría las palabras "nunca y" siempre ", pero en general veo que esta regla es útil. En algunos idiomas (Lisp, Smalltalk) esto no es un problema.

Utilice siempre un bloque {}, incluso para una sola línea

Nunca hago eso y nunca tuve un solo problema, pero puedo ver cómo puede ser bueno para los estudiantes, especialmente. si estudiaron Python antes.

Objeto Const en el lado izquierdo de la comparación

¿Condiciones de Yoda? No por favor. Duele la legibilidad. Simplemente use el nivel de advertencia máximo cuando compile su código.

Use unsigned para variables que son> = 0

OKAY. Curiosamente, he oído que Stroustrup no está de acuerdo.

Establezca el puntero en NULL después de la eliminación: doble protección de eliminación

¡Mal consejo! Nunca tenga un puntero que apunte a un objeto eliminado o inexistente.


55
+1 solo por el último punto solo. Un puntero en bruto no tiene por qué tener memoria de todos modos.
Konrad Rudolph

2
Con respecto al uso sin firmar: no solo Stroustrup, sino K&R (en C), Herb Sutter y (creo) Scott Meyers. De hecho, nunca escuché a nadie que realmente entendiera las reglas de C ++ argumentar a favor de usar sin firmar.
James Kanze

2
@JamesKanze De hecho, en la misma ocasión que escuché la opinión de Stroustrup (una conferencia de Boston en 2008), Herb Sutter estuvo allí y no estuvo de acuerdo con Bjarne en el acto.
Nemanja Trifunovic

3
Solo para completar " unsignedestá roto", uno de los problemas es que cuando C ++ compara tipos con y sin signo de tamaño similar, se convierte en uno sin signo antes de hacer la comparación. Lo que resulta en un cambio de valor. La conversión al firmado no necesariamente sería mucho mejor; la comparación realmente debería tener lugar "como si" ambos valores se convirtieran a un tipo más grande que pudiera representar todos los valores en cualquier tipo.
James Kanze

1
@SteveJessop Creo que debes tomarlo en el contexto de una función que regresa unsigned. Estoy seguro de que no tiene problemas para exp(double)devolver un valor superior a MAX_INT:-). Pero una vez más, el verdadero problema son las conversiones implícitas. int i = exp( 1e6 );Es perfectamente válido C ++. Stroustrup en realidad propuso despreciar las conversiones implícitas con pérdidas en un punto, pero el comité no estaba interesado. (Una pregunta interesante: sería unsigned-> intse consideraría con pérdida. Consideraría tanto unsigned-> intcomo int-> unsignedcon pérdida. Lo que contribuiría en gran medida a hacer que unsignedOK
James Kanze

18

Es más intuitivo y fácil de entender. Deja en claro la intención.

Y garantiza que el código no se rompa cuando un nuevo usuario podría perderlo sin saberlo {, al }tiempo que agrega una nueva declaración de código.


Makes the intent clear+1, esta es probablemente la razón más concisa y precisa.
Qix - MONICA FUE MALTRATADA el

13

Para agregar a las sugerencias muy sensatas anteriores, un ejemplo que encontré al refactorizar algún código de donde esto se vuelve crítico fue el siguiente: estaba alterando una base de código muy grande para cambiar de una API a otra. La primera API recibió una llamada para establecer el Id. De la compañía de la siguiente manera:

setCompIds( const std::string& compId, const std::string& compSubId );

mientras que el reemplazo necesitaba dos llamadas:

setCompId( const std::string& compId );
setCompSubId( const std::string& compSubId );

Me puse a cambiar esto usando expresiones regulares que fue muy exitoso. También pasamos el código a través de un estilo , lo que realmente lo hizo mucho más legible. Luego, a mitad del proceso de revisión, descubrí que en algunas circunstancias condicionales estaba cambiando esto:

if ( condition )
   setCompIds( compId, compSubId );

A esto:

if ( condition )
   setCompId( compId );
setCompSubId( compSubId );

que claramente no es lo que se requería. Tuve que volver al principio para hacer esto nuevamente tratando el reemplazo como completamente dentro de un bloque y luego alterando manualmente cualquier cosa que terminara pareciendo tonta (al menos no sería incorrecto).

Noto que astyle ahora tiene la opción --add-bracketsque le permite agregar corchetes donde no los hay y lo recomiendo si alguna vez se encuentra en la misma posición que yo.


55
Una vez vi algo de documentación que tenía la maravillosa moneda "Microsoftligent". Sí, es posible cometer errores significativos con la búsqueda y reemplazo global. Eso solo significa que la búsqueda y el reemplazo global deben usarse de manera inteligente, no de manera microsoftligente.
Pete Becker

44
Sé que este no es mi post-mortem para realizar, pero si vas a hacer un reemplazo de texto en el código fuente, entonces debes hacerlo de acuerdo con las mismas reglas que usarías para el tipo de reemplazo de texto que está bien establecido en el idioma: macros. No debe escribir una macro #define FOO() func1(); \ func2(); (con un salto de línea después de la barra invertida), lo mismo ocurre con la búsqueda y el reemplazo. Dicho esto, he visto "usar siempre llaves" avanzadas como una regla de estilo precisamente porque le evita envolver todas sus macros de múltiples declaraciones do .. while(0). Pero no estoy de acuerdo.
Steve Jessop

2
Por cierto, eso está "bien establecido" en el sentido de que el knotweed japonés está bien establecido: no estoy diciendo que debamos hacer todo lo posible para usar macros y reemplazo de texto, pero estoy diciendo que cuando hacemos tal , deberíamos hacerlo de una manera que funcione, en lugar de hacer algo que solo funcione si una regla de estilo particular se impuso con éxito en toda la base del código :-)
Steve Jessop

1
@SteveJessop One también podría argumentar por aparatos ortopédicos y un cinturón. Si tiene que usar tales macros (y lo hicimos, antes de C ++ y inline), entonces probablemente debería apuntar a que funcionen de la manera más parecida posible a una función, utilizando el do { ... } while(0)truco si es necesario (y muchos paréntesis adicionales. Pero eso todavía no funcionaría) no impida que use llaves en todas partes, si ese es el estilo de la casa. (FWIW: He trabajado en lugares con diferentes estilos de casa, cubriendo todos los estilos discutidos aquí. Nunca he encontrado que ninguno sea un problema serio).
James Kanze

1
Y creo que cuantos más estilos haya trabajado, más cuidadosamente leerá y editará el código. Por lo tanto, incluso si tiene preferencia por lo que es más fácil de leer, seguirá leyendo con éxito los otros. Trabajé en una compañía donde los diferentes equipos fueron escritos en diferentes "estilos de casa" por los diferentes equipos, y la solución correcta es quejarse en el comedor sin gran efecto, no tratar de crear un estilo global :-)
Steve Jessop

8

Estoy usando en {}todas partes, excepto en algunos casos donde es obvio. Una sola línea es uno de los casos:

if(condition) return; // OK

if(condition) // 
   return;    // and this is not a one-liner 

Puede dolerle cuando agrega algún método antes de regresar. La sangría indica que el retorno se está ejecutando cuando se cumple la condición, pero siempre regresará.

Otro ejemplo en C # con el uso de declaración

using (D d = new D())  // OK
using (C c = new C(d))
{
    c.UseLimitedResource();
}

que es equivalente a

using (D d = new D())
{
    using (C c = new C(d))
    {
        c.UseLimitedResource();
    }
}

1
Simplemente use comas en la usingdeclaración y no tiene que hacerlo :)
Ry-

1
@minitech Eso simplemente no funciona aquí: solo puede usar la coma cuando los tipos son iguales, no para tipos desiguales. La forma de Lukas de hacer esto es la forma canónica, el IDE incluso formatea esto de manera diferente (tenga en cuenta la falta de sangría automática de la segunda using).
Konrad Rudolph

8

El ejemplo más pertinente que se me ocurre:

if(someCondition)
   if(someOtherCondition)
      DoSomething();
else
   DoSomethingElse();

¿Con ifqué elsese emparejará? La sangría implica que el externo ifobtiene el else, pero en realidad no es así como lo verá el compilador; lo interno if obtendrá lo elsey lo externo ifno. Tendría que saber eso (o ver que se comporta de esa manera en el modo de depuración) para descubrir por inspección por qué este código puede estar fallando sus expectativas. Se vuelve más confuso si conoce Python; en ese caso, sabe que la sangría define bloques de código, por lo que esperaría que se evalúe de acuerdo con la sangría. C #, sin embargo, no da una vuelta rápida sobre el espacio en blanco.

Ahora, dicho esto, no estoy particularmente de acuerdo con esta regla de "siempre usar corchetes" en su cara. Hace que el código sea muy ruidoso verticalmente, lo que reduce la capacidad de leerlo rápidamente. Si la declaración es:

if(someCondition)
   DoSomething();

... entonces debería escribirse así. La frase "siempre usar corchetes" suena como "siempre rodear las operaciones matemáticas con paréntesis". Eso convertiría la declaración muy simple a * b + c / den ((a * b) + (c / d)), introduciendo la posibilidad de perderse un paréntesis cercano (la ruina de muchos codificadores), ¿y para qué? El orden de las operaciones es bien conocido y bien aplicado, por lo que los paréntesis son redundantes. Solo usaría paréntesis para imponer un orden de operaciones diferente al que normalmente se aplicaría: a * (b+c) / dpor ejemplo. Las llaves de bloque son similares; utilícelos para definir lo que desea hacer en casos en los que difiere del valor predeterminado y no es "obvio" (subjetivo, pero generalmente de sentido común).


3
@AlexBrown ... que era exactamente mi punto. La regla según lo establecido en el OP es "usar siempre corchetes, incluso para líneas simples", con lo que no estoy de acuerdo por la razón que dije. Soportes serían ayudar con el primer ejemplo de código, debido a que el código no se comportan de la manera que es sangría; tendrías que usar corchetes para emparejar elseel primero con el primero en iflugar del segundo. Por favor, elimine el voto negativo.
KeithS

5

Mirando las respuestas, nadie declaró explícitamente el tipo de práctica que uso, contando la historia de su código:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

Se convierte en:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0) j++;
}

Poniendo el j++en la misma línea que el if debería indicar a cualquier otra persona, "Solo quiero que este bloque se incremente j" . Por supuesto, esto solo vale la pena si la línea es lo más simplista posible, porque poner un punto de interrupción aquí, como se dice , no será muy útil.

De hecho, acabo de encontrarme con parte de la API de Twitter Storm que tiene este 'tipo' de código en Java, aquí está el fragmento relevante del código de ejecución, en la página 43 de esta presentación de diapositivas :

...
Integer Count = counts.get(word);
if (Count=null) count=0;
count++
...

El bloque de bucle for tiene dos cosas, por lo que no incluiría ese código. Es decir, nunca :

int j = 0;
for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) j++;

Es horrible y ni siquiera sé si funciona (según lo previsto); no haga esto . Las nuevas líneas y llaves ayudan a distinguir piezas de código separadas pero relacionadas, de la misma manera que una coma o un punto y coma lo hacen en prosa. El bloque anterior es tan malo como una oración muy larga con algunas cláusulas y algunas otras declaraciones que nunca se rompen o se detienen para distinguir partes separadas.

Si realmente quiere telegrafiar a alguien más, es un trabajo de una sola línea, use un operador ternario o un ?:formulario:

for (int i = 0 ; i < 100 ; ++i) (i%2 ? 0 : >0) j++;

Pero esto está al borde del código de golf, y creo que no es una buena práctica (no me queda claro si debo poner el j ++ en un lado del :o no). Nota : no he ejecutado un operador ternario en C ++ antes, no sé si esto funciona, pero existe .

En breve:

Imagine cómo su lector (es decir, la persona que mantiene el código) interpreta su historia (código). Que sea lo más claro posible para ellos. Si sabe que el codificador / estudiante novato está manteniendo esto, tal vez incluso deje {}entrar la mayor cantidad posible, solo para que no se confundan.


1
(1) Poner la declaración en la misma línea hace que sea menos legible, no más. Especialmente simple piensa que un incremento se pasa por alto fácilmente. Do los puso en una nueva línea. (2) Por supuesto, puede poner su forbucle en una sola línea, ¿por qué no debería funcionar esto? Funciona por la misma razón por la que puede omitir las llaves; la nueva línea simplemente no es significativa en C ++. (3) Su ejemplo de operador condicional, además de ser horrible, no es válido en C ++.
Konrad Rudolph

@ Konrad Rudolph gracias, estoy un poco oxidado en C ++. Nunca dije que (1) fuera más legible, pero sería una señal de que ese código estaba destinado a ser una línea en línea. (2) Mi comentario fue más que no podría leerlo y saber que funcionó, en absoluto o según lo previsto; Es un ejemplo de lo que no se debe hacer por este motivo. (3) Gracias, no he escrito C ++ en mucho tiempo. Lo arreglaré ahora.
Pureferret

2
Además, poner más de una expresión en una línea hace que sea más difícil depurar el código. ¿Cómo se coloca el punto de interrupción en la segunda expresión en esa línea?
Piotr Perak

5

Porque cuando tienes dos declaraciones sin {}, es fácil pasar por alto un problema. Supongamos que el código se ve así.

int error = 0;
enum hash_type hash = SHA256;
struct hash_value *hash_result = hash_allocate();

if ((err = prepare_hash(hash, &hash_result))) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &client_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &server_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &exchange_params)) != 0)
    goto fail;
    goto fail;
if ((err = hash_finish(hash)) != 0)
    goto fail;

error = do_important_stuff_with(hash);

fail:
hash_free(hash);
return error;

Se ve bien. El problema con esto es realmente fácil de pasar por alto, especialmente cuando la función que contiene el código es mucho más grande. El problema es que goto failse ejecuta incondicionalmente. Puedes imaginar fácilmente lo frustrante que es esto (haciéndote preguntar por qué hash_updatesiempre falla el último , después de todo, todo hash_updatefunciona bien)

Sin embargo, eso no significa que sea para agregar en {}todas partes (en mi opinión, ver en {}todas partes es molesto). Si bien puede causar problemas, nunca lo hizo para mis propios proyectos, ya que mi estilo de codificación personal prohíbe los condicionales sin {}cuando no están en la misma línea (sí, estoy de acuerdo en que mi estilo de codificación no es convencional, pero me gusta, y yo use el estilo de código del proyecto cuando contribuya a otros proyectos). Esto hace que el siguiente código esté bien.

if (something) goto fail;

Pero no el siguiente.

if (something)
    goto fail;

Exactamente. Simplemente no ponga la nueva línea + sangría (completamente innecesaria), y elude por completo este problema que todos siempre son tan rápidos de mencionar.
Alexander - Restablece a Mónica

4

Hace que su código sea más legible al definir claramente el alcance de sus bucles y bloques condicionales. También te salva de errores accidentales.


4

wrt 6: es más seguro porque eliminar un puntero nulo es un no-op. Entonces, si accidentalmente pasa por esa ruta dos veces, no causará que la corrupción de la memoria libere memoria que está libre o se ha asignado a otra cosa.

Esto es principalmente un problema con los objetos de ámbito de archivo estático y los singletons que no tienen vidas muy claras y se sabe que se recrearon después de haber sido destruidos.

En la mayoría de los casos, puede evitar la necesidad de esto utilizando auto_ptrs


66
Si pasa por ese camino dos veces, tiene un error de programación. Establecer un puntero en nulo para que este error sea menos dañino no resuelve el problema subyacente.
Pete Becker

1
De acuerdo, pero he visto esto recomendado antes, y creo que está en algunos estándares de programación profesional. Estaba comentando más sobre por qué el profesor del afiche lo había ideado, en lugar de cuándo era bueno
Tom Tanner,

2
Después de lo que dijo Pete Becker: no resuelve el problema subyacente, pero puede enmascararlo. (Hay casos en los que establecería un puntero NULLdespués de eliminarlo. Si NULLes un valor correcto para el puntero en esas circunstancias; por ejemplo, el puntero apunta a un valor en caché e NULLindica un caché no válido. Pero cuando ve a alguien configurando un puntero a NULLcomo la última línea en un destructor, te preguntas si conoce C ++.)
James Kanze

4

Me gusta la respuesta aceptada de Luchian, de hecho, aprendí por las malas que tiene razón, por lo que siempre uso llaves, incluso para bloques de una sola línea. Sin embargo, personalmente hago una excepción cuando escribo un filtro, como está en su ejemplo. Esta:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

me parece abarrotado. Separa el ciclo for y la declaración if en acciones separadas, cuando realmente su intención es una sola acción: contar todos los enteros divisibles por 2. En un lenguaje más expresivo, esto podría escribirse algo así como:

j = [1..100].filter(_%2 == 0).Count

En los idiomas que carecen de cierres, el filtro no se puede expresar en una sola declaración, sino que debe ser un ciclo for seguido de una declaración if. Sin embargo, todavía es una acción en la mente del programador, y creo que debería reflejarse en el código, así:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
  if (i % 2 == 0)
{
    j++;
}

77
Me gusta cómo todo el mundo logra ignorar for (int i = 0; i < 100; i += 2);, en aras de continuar la discusión sobre la sangría ;-) Probablemente haya una pelea de combates completamente diferente que podamos tener, cómo "mejor" expresar la lógica "para cada uno ien un cierto rango con una determinada propiedad". en C ++ sin bucle, utilizando una combinación de pesadilla de algoritmos estándar, filter_iteratory / o counting_iterator.
Steve Jessop

1
Además, si tuviéramos eso, podríamos estar en desacuerdo sobre cómo sangrar la declaración única masiva resultante.
Steve Jessop

2
@ Steve, es solo un ejemplo. Hay muchos usos legítimos del patrón. Obviamente, si desea contar los números del 1 al 100 que son divisibles por 2, todo lo que tiene que hacer es 100/2.
MikeFHay

2
Claro, lo sé, es por eso que abstraje "para cada uno ien un determinado rango con una determinada propiedad". Es solo que generalmente en SO, las personas son muy rápidas para ignorar la pregunta real a favor de un enfoque completamente diferente al ejemplo dado. Pero la sangría es importante , por lo que no lo hacemos ;-)
Steve Jessop

4

Una opción para ayudar a prevenir los errores que se han descrito anteriormente es alinear lo que desea que suceda cuando no usa llaves. Hace que sea mucho más difícil no notar los errores cuando intentas modificar el código.

if (condition) doSomething();
else doSomethingElse();

if (condition) doSomething();
    doSomething2(); // Looks pretty obviously wrong
else // doSomethingElse(); also looks pretty obviously wrong

55
La segunda opción generaría un error de compilación, ya elseque no está asociado con un if.
Luchian Grigore

Un problema no tan visible con el en línea es que la mayoría de los IDE por defecto lo cambian al estilo sangrado al usar su utilidad de autoformato.
Honza Brabec

@Honza: sin embargo, ese es un tema político muy cargado. Si estamos cooperando en una base de código, o tenemos que usar el mismo estilo de sangría hasta el último detalle, o ambos tenemos que estar de acuerdo en no autoformar el código existente "solo porque". Si es lo primero, el estilo acordado aún podría incluir esto, pero tendría que configurar su IDE para respetarlo o no usar autoformato. Estar de acuerdo en que el formato común es "lo que sea para mis autoformatos IDE" está muy bien si todos usamos el mismo IDE para siempre, de lo contrario no sería tan bueno.
Steve Jessop

3

Si eres un compilador, no hace ninguna diferencia. Los dos son iguales.

Pero para los programadores, el primero es más claro, fácil de leer y menos propenso a errores.


2
Aparte de abrir {en su propia línea, de todos modos.
Rob

3

Otro ejemplo de agregar llaves. Una vez que estaba buscando un error y encontré dicho código:

void SomeSimpleEventHandler()
{
    SomeStatementAtTheBeginningNumber1;
    if (conditionX) SomeRegularStatement;
    SomeStatementAtTheBeginningNumber2;
    SomeStatementAtTheBeginningNumber3;
    if (!SomeConditionIsMet()) return;
    OtherwiseSomeAdditionalStatement1;
    OtherwiseSomeAdditionalStatement2;
    OtherwiseSomeAdditionalStatement3;
}

Si lee el método línea por línea, notará que hay una condición en el método que se devuelve si no es cierto. Pero en realidad se parece a otros 100 controladores de eventos simples que establecen algunas variables en función de algunas condiciones. Y un día aparece el Codificador rápido y agrega una declaración de configuración de variables adicional al final del método:

{
    ...
    OtherwiseSomeAdditionalStatement3;
    SetAnotherVariableUnconditionnaly;
}

Como resultado, SetAnotherVariableUnconditionnaly se ejecuta cuando SomeConditionIsMet (), pero el tipo rápido no lo notó porque todas las líneas tienen un tamaño casi similar e incluso cuando la condición de retorno está indentada verticalmente no es tan notable.

Si el retorno condicional se formatea así:

if (!SomeConditionIsMet())
{
    return;
}

es muy notable y Fast Coder lo encontrará de un vistazo.


Si su codificador rápido no puede molestarse en detectar una returndeclaración resaltada de sintaxis dentro del cuerpo de una función antes de agregarle algo, no debe permitir que el codificador rápido se acerque a su código. No evitarás que un tipo así mire tu código al incluir llaves.
cmaster - reinstalar a monica

@cmaster Él ya no trabaja con nosotros. De todos modos, el resaltado de sintaxis es bueno, pero recuerde que hay personas que no ven con claridad (incluso vi una publicación de un programador ciego el año pasado).
Artemix

2

Considero que el primero es claro y luego el segundo. Da la sensación de cerrar las instrucciones, con poco código está bien cuando el código se vuelve complejo {...}ayuda mucho incluso si es endifobegin...end

//first
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}


//second
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;

1

Es mejor establecer el puntero en NULL cuando haya terminado con él.

Aquí hay un ejemplo de por qué:

La clase A hace lo siguiente:

  1. Asigna un bloque de memoria
  2. Luego, algún tiempo después, elimina este bloque de memoria pero no establece el puntero en NULL

La clase B hace lo siguiente

  1. Asigna memoria (y en este caso se le da el mismo bloque de memoria que fue eliminado por la clase A.)

En este punto, tanto la Clase A como la Clase B tienen punteros que apuntan al mismo bloque de memoria, en lo que respecta a la Clase A, este bloque de memoria no existe porque ha terminado con él.

Considere el siguiente problema:

¿Qué sucede si hubo un error lógico en la Clase A que resultó en la escritura en la memoria que ahora pertenece a la Clase B?

En este caso particular, no obtendrá un error de excepción de acceso incorrecto porque la dirección de memoria es legal, mientras que la clase A ahora está corrompiendo efectivamente los datos de clase B.

La clase B puede bloquearse eventualmente si encuentra valores inesperados y, cuando se bloquea, lo más probable es que pases bastante tiempo buscando este error en la clase B cuando el problema está en la clase A.

Si hubiera establecido el puntero de memoria eliminado en NULL, habría recibido un error de excepción tan pronto como cualquier error lógico en la Clase A intentara escribir en el puntero NULL.

Si le preocupa el error lógico con doble eliminación cuando los punteros son NULL por segunda vez, agregue aserción para esto.

Además: si va a rechazar el voto, explique.


2
Si hubo un error lógico, debería solucionarse, en lugar de enmascararlo.
Uınbɐɥs

@Barmar, OP dice ... 6. Establezca el puntero en NULL después de la eliminación - Protección de eliminación doble // no está mal. Algunas personas han respondido sobre no establecerlo en Null y estoy diciendo por qué debería establecerse en NULL, ¿qué parte de 6. mi respuesta para establecer NULL no cabe en 6?
John

1
@ Shaquin, ¿y cómo propones encontrar estos errores lógicos en primer lugar? Una vez que establezca la variable del puntero en NULL después de que se haya eliminado la memoria. Cualquier intento de hacer referencia al puntero NULL se bloqueará con el depurador en la línea en que se realizó su intento ilegal. Puede rastrear y ver dónde estaba el error lógico y solucionar el problema. Si no establece la variable de puntero en NULL después de haber eliminado la memoria, su intento ilegal de escribir esta memoria eliminada debido a errores lógicos de UNAWARE puede tener éxito y, por lo tanto, no se bloquea en ese punto. No lo está enmascarando.
John

1

Siempre tener llaves es una regla muy simple y robusta. Sin embargo, el código puede parecer poco elegante cuando hay muchas llaves. Si las reglas permiten omitir llaves, entonces debería haber reglas de estilo más detalladas y herramientas más sofisticadas. De lo contrario, puede resultar fácilmente con un código caótico y confuso (no elegante). Por lo tanto, buscar una regla de estilo individual separada del resto de guías de estilo y herramientas utilizadas es probablemente infructuoso. Solo traeré algunos detalles importantes sobre esa regla # 3 que ni siquiera se han mencionado en otras respuestas.

El primer detalle interesante es que la mayoría de los defensores de esa regla acuerdan violarla en caso de else. En otras palabras, no exigen en revisión dicho código:

// pedantic rule #3
if ( command == Eat )
{
    eat();
}
else
{
    if ( command == Sleep )
    {
        sleep();
    }
    else
    {
        if ( command == Drink )
        {
            drink();
        }
        else
        {
            complain_about_unknown_command();
        }
    }
}

En cambio, si lo ven, incluso pueden sugerir que lo escriban así:

// not fully conforming to rule #3
if ( command == Eat )
{
    eat();
}
else if ( command == Sleep )
{
    sleep();
}
else if ( command == Drink )
{
    drink();
}
else
{
   complain_about_unknown_command();
}

Eso es técnicamente una violación de esa regla ya que no hay llaves entre elsey if. Tal dualidad de la regla aparece cuando se intenta aplicarla a la base de código automáticamente con una herramienta sin sentido. De hecho, por qué discutir, simplemente deje que una herramienta aplique el estilo automáticamente.

El segundo detalle (que a menudo también olvidan los defensores de esa regla) es que los errores que pueden ocurrir nunca se deben solo a violaciones de esa regla # 3. En realidad, casi siempre implican violaciones de la regla n. ° 1 (con las que nadie discute). Nuevamente, desde el punto de vista de las herramientas automáticas, no es difícil hacer una herramienta que se queje inmediatamente cuando se infringe la regla n. ° 1, por lo que la mayoría de los errores pueden detectarse a tiempo.

El tercer detalle (que a menudo los opositores olvidan de esa regla) es la naturaleza confusa de la declaración vacía que está representada por un punto y coma. La mayoría de los desarrolladores con algo de experiencia se confundieron tarde o temprano por un punto y coma fuera de lugar o por una declaración vacía que se escribe con un punto y coma. Dos llaves en lugar de un punto y coma son visualmente más fáciles de detectar.


1

Tengo que admitir que no siempre se usa {}para una sola línea, pero es una buena práctica.

  • Digamos que escribe un código sin paréntesis que se ve así:

    para (int i = 0; i <100; ++ i) para (int j = 0; j <100; ++ j) DoSingleStuff ();

Y después de un tiempo, desea agregar en jbucle algunas otras cosas y simplemente lo hace alineando y olvidando agregar corchetes.

  • La desasignación de memoria es más rápida. Digamos que tiene un gran alcance y crea grandes matrices en el interior (sin newque estén en la pila). Esos arreglos se eliminan de la memoria justo después de que deja el alcance. Pero es posible que use esa matriz en un lugar y estará en la pila por un tiempo y será una especie de basura. Como una pila tiene un tamaño limitado y bastante pequeño, es posible superar el tamaño de la pila. Entonces, en algunos casos, es mejor escribir {}para evitar eso. NOTA: esto no es para una sola línea, sino para tales situaciones:

    if (...) {// SomeStuff ... {// no tenemos if, while, etc. // SomeOtherStuff} // SomeMoreStuff}

  • La tercera forma de usarlo es similar a la segunda. Simplemente no es para hacer una pila más limpia sino para abrir algunas funciones. Si usa mutexfunciones largas, generalmente es mejor bloquear y desbloquear justo antes de acceder a los datos y justo después de terminar de leer / escribir eso. NOTA de esta manera se usa si tiene alguna propia classo structcon constructory destructorpara bloquear la memoria.

  • Qué es más:

    if (...) if (...) SomeStuff (); else SomeOtherStuff (); // va al segundo si, pero la alineación muestra que está encendido primero ...

En general, no puedo decir, cuál es la mejor manera de usar siempre {}para una sola línea, pero no es nada malo hacer eso.

EDICIÓN IMPORTANTE Si escribe corchetes de compilación de código para una sola línea no hace nada, pero si su código será interpretado, ralentiza el código muy ligeramente. Muy ligeramente.


1

Hay varias formas posibles de escribir declaraciones de control; ciertas combinaciones de ellas pueden coexistir sin afectar la legibilidad, pero otras combinaciones causarán problemas. El estilo

if (condition)
  statement;

coexistirá cómodamente con algunas de las otras formas de escribir declaraciones de control, pero no tan bien con otras. Si las declaraciones controladas de varias líneas se escriben como:

if (condition)
{
  statement;
  statement;
}

entonces será visualmente obvio qué ifafirmaciones controlan una sola línea y cuáles controlan varias líneas. Sin embargo, si las ifdeclaraciones de varias líneas se escriben como:

if (condition) {
  statement;
  statement;
}

entonces la probabilidad de que alguien intente extender una ifconstrucción de una sola declaración sin agregar las llaves necesarias puede ser mucho mayor.

La declaración de una sola declaración en la línea siguiente iftambién puede ser problemática si la base de código hace un uso significativo del formulario

if (condition) statement;

Mi preferencia es que tener la declaración en su propia línea generalmente mejora la legibilidad, excepto en los casos en que hay muchas ifdeclaraciones con bloques de control similares, por ejemplo

if (x1 > xmax) x1 = xmax;
if (x1 < xmin) x1 = xmin;
if (x2 > xmax) x2 = xmax;
if (x2 < xmin) x2 = xmin;
etc.

en cuyo caso generalmente precederé y seguiré dichos grupos de ifdeclaraciones con una línea en blanco para separarlas visualmente de otro código. Tener una gama de declaraciones que comiencen con ifla misma sangría proporcionará una indicación visual clara de que hay algo inusual.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.