style guide google code c++ coding-style

c++ - guide - google code style java



Elementos más cruciales en un estándar ligero de codificación C++ (30)

Cuidado con la API C

La API C puede ser muy eficiente, pero necesitará datos brutos expuestos (es decir, punteros, etc.), lo que no ayudará a la seguridad del código. Utilice API C ++ existente en su lugar, o encapsule la API C con código C ++.

p.ej:

// char * d, * s ; strcpy(d, s) ; // BAD // std::string d, s ; d = s ; // GOOD

Nunca use strtok

strtok no es reentrante. Lo que significa que si se inicia un strtok mientras otro no termina, se corromperán los "datos internos" del otro.

¿Cómo facilita claramente el código más seguro, lo que minimiza el riesgo de errores enigmáticos, lo que aumenta la capacidad de mantenimiento, etc.?

Usar C API significa usar tipos crudos, lo que puede llevar a errores interesantes como desbordamiento de búfer (y potencial corrupción de pila) cuando un sprintf va demasiado lejos (o string cropping cuando se usa snprintf, que es un tipo de corrupción de datos). Incluso cuando se trabaja con datos sin procesar, malloc se puede abusar fácilmente, como se muestra en el siguiente código:

int * i = (int *) malloc(25) ; // Now, I BELIEVE I have an array of 25 ints! int * j = new int[25] ; // Now, I KNOW I have an array of 25 ints!

Etcétera etcétera..

En cuanto a strtok: C y C ++ son lenguajes aptos para la pila, que le permiten al usuario no preocuparse por las funciones que están por encima de las suyas en la pila, y las funciones que se llamarán debajo de las suyas en la pila. strtok elimina esta libertad de "no preocuparse"

He estado involucrado en el desarrollo de estándares de codificación que fueron bastante elaborados. Mi propia experiencia es que fue difícil aplicarla si no tienes los procesos adecuados para mantenerla y las estrategias para mantenerla.

Ahora estoy trabajando y liderando un entorno aún menos probable de tener procesos y estrategias de seguimiento en bastante tiempo. Sin embargo, quiero mantener un nivel mínimo de código respetable. Así que pensé que obtendría buenas sugerencias aquí, y que juntos podríamos producir un subconjunto razonablemente liviano de las prácticas estándares de codificación más importantes para que otros las usen como referencia.

Entonces, para enfatizar la esencia aquí:

¿Qué elementos de un estándar de codificación de C ++ son los más importantes para mantener?

  • Contestando / reglas de votación

    • 1 candidato por respuesta, preferentemente con una breve motivación.

    • Vote por los candidatos que se enfoca en el estilo y las pautas de formato subjetivas. Esto no significa que no sean importantes, solo que son menos relevantes en este contexto.

    • Vote a los candidatos centrándose en cómo comentar / documentar el código. Este es un tema más amplio que incluso podría merecer su propia publicación.

    • Vote por los candidatos que claramente facilita un código más seguro, lo que minimiza el riesgo de errores enigmáticos, lo que aumenta la capacidad de mantenimiento, etc.

    • No emitas tu voto en ninguna dirección sobre candidatos de los que no estés seguro. Incluso si suenan razonables e inteligentes, o por el contrario "algo que seguramente nadie usaría", su voto debe basarse en una clara comprensión y experiencia.


La optimización temprana es la raíz de todo mal

Escribe código seguro y correcto primero.

Entonces, si tiene problemas de rendimiento, y si su perfilador le dijo que el código es lento, puede intentar optimizarlo.

Nunca creas que optimizarás fragmentos de código mejor que el compilador.

Cuando busque optimizaciones, estudie los algoritmos utilizados y potencialmente mejores alternativas.

¿Cómo facilita claramente el código más seguro, lo que minimiza el riesgo de errores enigmáticos, lo que aumenta la capacidad de mantenimiento, etc.?

Por lo general, el código "optimizado" (o supuestamente optimizado) es mucho menos claro y tiende a expresarse a través de una forma cruda y cercana a la máquina, en lugar de una forma más orientada a los negocios. Algunas optimizaciones dependen de los conmutadores, si, etc., y luego serán más difíciles de probar debido a múltiples rutas de código.

Y, por supuesto, la optimización antes del perfilado a menudo conduce a cero ganancia de rendimiento.


Nunca use estructuras sin los constructores apropiados

las estructuras son constructos legales de C ++, que se utilizan para agregar datos juntos. Aún así, los datos deben estar siempre correctamente inicializados.

Todas las estructuras de C ++ deben tener al menos un constructor predeterminado, que establecerá sus datos agregados a los valores predeterminados.

struct MyStruct // BAD { int i ; bool j ; char * k ; } struct MyStruct // GOOD { MyStruct() : i(0), j(true), k(NULL) : {} int i ; bool j ; char * k ; }

Y si generalmente se inicializan de alguna manera, proporcione un constructor para permitir al usuario evitar una inicialización de estructura de estilo C:

MyStruct oMyStruct = { 25, true, "Hello" } ; // BAD MyStruct oMyStruct(25, true, "Hello") ; // GOOD

¿Cómo facilita claramente el código más seguro, lo que minimiza el riesgo de errores enigmáticos, lo que aumenta la capacidad de mantenimiento, etc.?

Tener struct sin un constructor apropiado deja al usuario de esta estructura la tarea de inicializarlo. Por lo tanto, el siguiente código se copiará pegado de la función a la función:

void doSomething() { MyStruct s = { 25, true, "Hello" } ; // Etc. } void doSomethingElse() { MyStruct s = { 25, true, "Hello" } ; // Etc. } // Etc.

Lo que significa que, en C ++, si necesita agregar un campo en la estructura o cambiar el orden de los datos internos, tiene que pasar por todas estas inicializaciones para verificar que cada uno sigue siendo correcto. Con un constructor apropiado, la modificación de las partes internas de las estructuras está desacoplada de su uso.


Sepa quién es el dueño de ese recuerdo.

  • crea objetos en la pila tanto como sea posible (no es inútil)
  • Evite la transferencia de propiedad a menos que realmente lo necesite
  • Use RAII y punteros inteligentes
  • Si la transferencia de propiedad es obligatoria (sin punteros inteligentes), entonces, documente claramente el código (las funciones deben tener un nombre no ambiguo, siempre usando el mismo patrón de nombre, como "char * allocateMyString ()" y "void deallocateMyString (char * pag)".

¿Cómo facilita claramente el código más seguro, lo que minimiza el riesgo de errores enigmáticos, lo que aumenta la capacidad de mantenimiento, etc.?

Al no tener una filosofía clara de propiedad de la memoria, se producen errores interesantes o pérdidas de memoria, y se pierde el tiempo preguntándose si el char * devuelto por esta función debe ser desasignado por el usuario, o no, o devuelto a una función de desasignación especial, etc.

Tanto como sea posible, la función / objeto que asigna la memoria debe ser la función / objeto que lo desasigna.


Use vectores y cadenas en lugar de arreglos de estilo C y caracteres *

Use std :: vector siempre que necesite crear un búfer de datos, incluso si el tamaño es fijo.

Usa std :: string cada vez que necesites una cadena.

¿Cómo facilita claramente el código más seguro, lo que minimiza el riesgo de errores enigmáticos, lo que aumenta la capacidad de mantenimiento, etc.?

std :: vector: el usuario de un vector siempre puede encontrar su tamaño, y el vector puede redimensionarse si es necesario. Incluso se puede dar (a través de la notación (& (myVector [0])) a una API de C. Por supuesto, el vector limpiará después de sí mismo.

std :: string: Casi las mismas razones anteriores. Y el hecho de que siempre se inicializará correctamente, que no se puede sobrepasar, que manejará las modificaciones correctamente, como concatenaciones, asignación, etc., y de forma natural (usando operadores en lugar de funciones)


Asegúrese de que el nivel de advertencia de su compilador esté configurado lo suficientemente alto (/ Wall preferiblemente) para que pueda detectar errores tontos como:

if (p = 0)

cuando realmente quisiste decir

if (p == 0)

para que no tengas que recurrir a trucos más tontos como:

if (0 == p)

que degradan la legibilidad de tu código.


Curly llaves para cualquier declaración de control. (Gracias a la propia experiencia y reforzado al leer Code Complete v2):

// bad example - what the writer wrote if( i < 0 ) printf( "%d/n", i ); ++i; // this error is _very_ easy to overlook! // good example - what the writer meant if( i < 0 ) { printf( "%d/n", i ); ++i; }


La herencia pública debe modelar el Principio de sustitución de Liskov (LSP).

La reutilización / importación de código sin sustituibilidad debe implementarse con herencia privada cuando tenga sentido un acoplamiento fuerte o, de lo contrario, agregue.


Métodos y nombres de variables en un esquema de denominación común para coherencia; No suelo molestarme demasiado por otra cosa mientras leo la fuente.


Mantenga las funciones a un tamaño razonable. Personalmente, me gusta mantener las funciones en menos de 25 líneas. La legibilidad se mejora cuando puede tomar una función como una unidad en lugar de tener que buscar hacia arriba y hacia abajo tratando de descubrir cómo funciona. Si tiene que desplazarse para leerlo, empeora las cosas.


No agregue tipos o funciones al espacio de nombres global.


Nota al margen: no imponga SESE ( Single Entry Single Exit ) (es decir, no prohíba más de una return , el uso de break / continue / ...)

En C ++, esto es una utopía ya que throw es otro punto de retorno. SESE tenía dos ventajas en C y lenguajes sin excepciones:

  • la liberación determinística de recursos que ahora se maneja ordenadamente mediante el modismo de RAII en C ++,
  • hacer que las funciones sean más fáciles de mantener, eso no debería ser una preocupación ya que las funciones deben mantenerse cortas (como lo especifica la regla de "una función, una responsabilidad")

Prefiere RAII .

Los indicadores automáticos de STL (y compartidos en impulso y C ++ 0x) pueden ayudar.


Prefiere el código que cumple con las normas. Prefiere usar las bibliotecas estándar.


Prohibir t[i]=i++; f(i++,i); , y así sucesivamente, ya que no hay garantías (portátiles) con respecto a lo que se ejecuta primero.


Sólo uso trivial de la? : operador, es decir

float x = (y > 3) ? 1.0f : -1.0f;

está bien, pero esto no es:

float x = foo(2 * ((y > 3) ? a : b) - 1);


Sean cuales sean las pautas, es muy fácil reconocer la aplicabilidad: cuanto menos opciones tenga, menos tiempo perderá en elegir. Y es más fácil descifrar el código.

Ejemplos de ''difícil de reconocer'':

  • Sin frenillos si solo hay una línea en el cuerpo condicional
  • Utilice la colocación de corchetes K & R para los espacios de nombres, pero coloque el corsé debajo de las condiciones en el código de definición de funciones
  • ...

Si la cadena de herramientas en uso (o el uso proyectado) tiene una implementación ineficiente de excepciones , podría ser conveniente evitar su uso. He trabajado en tales condiciones.

Actualización: aquí está el razonamiento de alguien más para "Embedded C ++", que parece excluir excepciones. Hace los siguientes puntos:

  • Es difícil estimar el tiempo transcurrido entre el momento en que se produjo una excepción y el control pasó a un manejador de excepciones correspondiente.
  • Es difícil estimar el consumo de memoria para el manejo de excepciones.

Hay texto más elaborado en esa página, no quería copiarlo todo. Además, tiene 10 años, por lo que podría no ser útil por más tiempo, por lo que incluí la parte sobre la cadena de herramientas. Quizás eso también debería decir "si la memoria no se considera un problema importante" y / o "si no se requiere una respuesta predecible en tiempo real", y así sucesivamente.


Use referencias en lugar de punteros cuando sea posible. Esto evita constantes comprobaciones NULL defensivas.


Use una herramienta de pelusa, es decir, PC-Lint. Esto captará muchos de los problemas de las directrices de codificación "estructurales". Lo que significa cosas que se leen a errores reales en lugar de problemas de estilo / legibilidad. (No es que la legibilidad no sea importante, pero es menor que los errores reales).

Ejemplo, en lugar de requerir este estilo:

if (5 == variable)

Como una forma de prevenir la falla de la "asignación no intencionada", permita que la pelusa la encuentre.


afirmar todas las suposiciones, incluidas las suposiciones temporales, como el comportamiento no implementado. afirmar las condiciones de entrada y salida de la función si no es trivial. afirmar todos los estados intermedios no triviales. su programa nunca debería fallar sin una afirmación que falla primero. puede personalizar su mecanismo de afirmación para ignorar futuras ocurrencias.

Use el código de manejo de errores para las condiciones que espera que ocurran; utilizar afirmaciones para condiciones que nunca deberían ocurrir. El manejo de errores generalmente verifica los datos de entrada incorrectos; las afirmaciones comprueban si hay errores en el código.

Si el código de manejo de errores se usa para tratar una condición anómala, el manejo de errores permitirá que el programa responda al error con gracia. Si se activa una afirmación para una condición anómala, la acción correctiva no es meramente para manejar un error con elegancia: la acción correctiva es cambiar el código fuente del programa, volver a compilar y lanzar una nueva versión del software. Una buena forma de pensar en las aserciones es como documentación ejecutable: no puede confiar en ellas para que el código funcione, pero pueden documentar las suposiciones más activamente que los comentarios en el lenguaje del programa pueden [1].

  1. McConnell, Steve. Código completo, segunda edición. Microsoft Press © 2004. Capítulo 8 - Programación defensiva

Las llaves se requiere si usted tiene más de un paso de muesca:

if (bla) { for (int i = 0; i < n; ++i) foo(); }

Esto ayuda a mantener la sangría de acuerdo con la forma en que el compilador ve el código.


Use los identificadores de const por defecto. Proporcionan garantías para el lector / mantenedor, y son mucho más fáciles de construir que insertar después.

Ambas variables miembro y métodos se declararán const , así como los argumentos de la función. const variables miembro de const imponen el uso adecuado de la lista de inicializadores.

Un efecto secundario de esta regla: evitar métodos con efectos secundarios.


Principio de la menor sorpresa .

Tal vez no es el "sabor" de las reglas que está buscando, pero definitivamente lo pondría primero.

No es solo la raíz, la razón y el control de cordura de todas las cosas aburridas, como las pautas de formato y comentario, sino que, para mí, lo más importante, pone el énfasis en el código que se lee y se comprende, en lugar de solo compilarse.

También cubre la única medida de calidad de código razonable que he encontrado: WTF por minuto .

Utilizaría ese primer punto para enfatizar la importancia y el valor de un código claro y consistente, y para motivar los siguientes elementos en el estándar de codificación.


Siempre, siempre, siempre realice la inicialización adecuada de los miembros de datos en la construcción del objeto.

Me encontré con un problema donde un constructor de objetos confiaba en una inicialización "predeterminada" para sus miembros de datos. Construir el código bajo dos plataformas (Windows / Linux) dio resultados diferentes y una falla de memoria difícil de encontrar. El resultado fue que un miembro de datos no se inicializó en el constructor y se usó antes de inicializarse. En una plataforma (Linux), el compilador lo inicializó a lo que el escritor de códigos consideró apropiado por defecto. En Windows, el valor se inicializó a algo, pero a la basura. Sobre el uso del miembro de datos, todo se volvió loco. Una vez que se solucionó la inicialización, ya no hay problema.


Probablemente una obviedad, pero sin embargo una regla importante:

Evite el comportamiento indefinido.

Hay una gran cantidad en C ++, y probablemente sea imposible escribir una aplicación no trivial que no dependa de alguna manera, pero la regla general debería ser que el "comportamiento indefinido es malo". (Porque lamentablemente hay programadores de C ++ que sienten que "funciona en mi máquina / compilador" es lo suficientemente bueno).

Si tiene que confiar en él, deje en claro a todos qué, por qué, dónde y cómo.


Use moldes de C ++ en lugar de moldes de C

utilizar:

  • static_cast
  • const_cast
  • reinterpret_cast
  • dynamic_cast

pero nunca arroja estilo C

Cómo facilita claramente el código más seguro, lo que minimiza el riesgo de errores enigmáticos, lo que aumenta la capacidad de mantenimiento, etc.

Cada lanzamiento tiene poderes limitados. Por ejemplo, si desea eliminar un const (por alguna razón), const_cast no cambiará el tipo al mismo tiempo (lo que podría ser un error difícil de encontrar).

Además, esto permite que un revisor los busque y luego el codificador los justifique si es necesario.


Evite usar el constructor de copia generado y operator = de manera predeterminada.

  • Si quieres que tu objeto sea copiable.
    • Si cada atributo puede copiarse trivialmente, comente claramente que está utilizando un constructor de copia implícito y operator = deliberadamente.
    • De lo contrario, escriba sus propios constructores, utilizando el campo de inicialización para inicializar los atributos y siguiendo el orden del encabezado (que es el orden de construcción real).
  • Si todavía no lo sabe ( opción predeterminada ) o cree que no desea copiar los objetos de una determinada clase, declare que el constructor de copia y el operador = son privados. De esta forma, el compilador te avisará cuando estás haciendo algo que no quieres hacer.

class foo { //... private: foo( const foo& ); const foo& operator=( const foo& ); };

O de una manera más limpia si estás usando boost:

class foo : private boost::noncopyable { ... };


Pasar argumentos de entrada por referencia const, y argumentos de salida o de entrada-salida por puntero. Esta es una regla borrwed de la guía de estilo de Google.

Solía ​​tener una aversión absoluta a los punteros y preferido utilizar referencias siempre que sea posible (como se sugiere por uno de los carteles en este hilo). Sin embargo, la adopción de esta convención-como-puntero de salida-arg ha hecho mis funciones mucho más legible. Por ejemplo,

SolveLinearSystem(left_hand_side, right_hand_side, &params);

deja claro que "params" se está escribiendo a.