¿Por qué es tan importante la revisión de código en la industria del software?

En uno de mis libros de software favoritos, Facts and Falacies of Software Engineering, de Robert Glass, Fact 37 señala:

Las inspecciones rigurosas pueden eliminar hasta el 90 por ciento de los errores de un producto de software antes de ejecutar el primer caso de prueba.

¡Y la mejor parte es que las revisiones son rentables!

Además, los mismos estudios muestran que el costo de las inspecciones es menor que el costo de las pruebas que serían necesarias para encontrar los mismos errores.

Uno de mis otros libros de software favoritos, Code Complete de Steve McConnell, señala que,

la tasa promedio de detección de defectos es solo del 25 por ciento para pruebas unitarias, 35 por ciento para pruebas de funcionamiento y 45 por ciento para pruebas de integración. En contraste, la efectividad promedio de las inspecciones de diseño y código es de 55 y 60 por ciento .

Tenga en cuenta que McConnell se refiere a la evidencia de la efectividad promedio, mientras que Glass se refiere a la evidencia de la efectividad máxima.
Sin embargo, la mejor parte es que Code Review no solo es útil para encontrar defectos. Es una excelente manera de difundir información sobre estándares y convenciones de codificación a otros, así como una gran herramienta de enseñanza. Aprendo mucho cuando mis compañeros revisan mi código y lo uso como una oportunidad para enseñar a otros que envían RP a mis proyectos.

Revisión efectiva del código
Notará que Glass y McConnell usan el término “inspección de código” y no revisan. Muchas veces, cuando pensamos en la revisión del código, pensamos simplemente en mirar el código un poco hacia arriba y hacia abajo, hacer algunos comentarios breves sobre errores evidentes evidentes y luego llamarlo un día.
Sé que he sido culpable de este enfoque de revisión de código “drive-by”. Es especialmente fácil de hacer con solicitudes de extracción.
Pero a lo que se refieren estos caballeros es a un enfoque mucho más exhaustivo y riguroso para revisar el código. Descubrí que cuando lo hago bien, una revisión adecuada del código es tan intensa y mentalmente agotadora como escribir código, si no más. Normalmente me gusta tomar una siesta después.
Aquí hay algunos consejos que he aprendido a lo largo de los años para hacer bien las revisiones de código.
Revise una cantidad razonable de código a la vez
Este es uno de los consejos más difíciles de seguir. Cuando comienzo una revisión de una solicitud de extracción, siento la tentación de terminarla de una sola vez porque soy impaciente y quiero volver a mi propio trabajo. Además, sé que otros están esperando la revisión y no quiero retrasarlos.
¡Pero trato de recordarme que la revisión de código es mi trabajo ! Además, una revisión mal hecha no es mucho mejor que ninguna revisión. Cuando te das cuenta de que las revisiones de código son importantes, entiendes que vale la pena el tiempo extra para hacerlo bien.
Por lo general, me detengo cuando llego al punto de agotamiento de la revisión y me encuentro saltando el código. Solo tomo un descanso, paso a otra cosa y vuelvo a hacerlo más tarde. ¿Qué mejor momento para ponerse al día con los episodios de Archer?
Centrarse en el código y no en el autor.
Esto tiene más que ver con el aspecto social de la revisión de código que con la búsqueda de defectos. Intento hacer todo lo posible para centrar mis comentarios en el código y no en la capacidad o el estado mental del autor. Por ejemplo, en lugar de preguntar “¡¿Qué demonios estabas pensando cuando escribiste esto ?!”, diré: “No tengo claro qué hace esta sección del código. ¿Lo explicaría usted?
¿Ver? En lugar de atacar al autor, me estoy centrando en el código y en mi comprensión del mismo.
Por supuesto, es posible seguir este consejo y seguir siendo insultante: “Este código me hace querer arrancarme los ojos entre mis ataques de vómitos”. Si bien esta oración se centra en el código y en cómo me hace sentir, todavía está implícitamente insultante para el autor. Intenta evitar eso.
Mantenga una lista de verificación de revisión de código
Una lista de verificación de revisión de código es una herramienta realmente excelente para llevar a cabo una revisión de código efectiva. La lista de verificación debe ser un recordatorio suave de problemas comunes en el código que desea revisar. No debería representar las únicas cosas que revisas, sino un conjunto mínimo. Siempre debe involucrar a su cerebro durante una revisión en busca de cosas que podrían no estar en su lista de verificación.
Seré honesto, cuando comencé a escribir esta publicación, solo tenía una lista de verificación mental que revisé. En un esfuerzo por evitar ser un hipócrita y subir de nivel mi revisión de código, creé una lista de verificación.
Mi lista de verificación incluye cosas como:

  1. Asegúrese de que haya pruebas unitarias y revise los que primero buscan vacíos en las pruebas. Las pruebas unitarias son una forma fantástica de comprender cómo los demás deben usar el código y aprender cuál es el comportamiento esperado.
  2. Revisar los argumentos de los métodos. Asegúrese de que los argumentos de los métodos tengan sentido y estén validados.
  3. Busque excepciones de referencia nula. Las referencias nulas son una perra y vale la pena buscarlas específicamente.
  4. Asegúrese de que los nombres, el formato, etc. sigan nuestras convenciones y sean coherentes. Me gusta una base de código que sea bastante consistente para que sepa qué esperar.
  5. Las cosas desechables se eliminan. Busque usos de los recursos que deberían eliminarse pero que no lo están.
  6. Seguridad. Existe un proceso completo de revisión de amenazas y mitigación que se incluye en este segmento. No voy a entrar en eso en esta publicación.

También tengo listas de verificación separadas para diferentes elementos específicos de la plataforma. Por ejemplo, si estoy revisando una aplicación WPF, estoy buscando casos en los que podamos actualizar la interfaz de usuario en un subproceso que no sea de interfaz de usuario. Ese tipo de cosas.

El proceso de revisión de código ahorra mucho tiempo porque es mucho más fácil y rápido corregir todos los errores de tipo, errores de seguridad, errores arquitectónicos y fallas de lógica de negocios antes de la compilación del producto. También es la forma de comunicación entre desarrolladores durante la cual los desarrolladores junior aumentan rápidamente su competencia a través de puntos de problemas de aprendizaje y los desarrolladores senior encuentran errores comunes.

Todos los miembros del equipo conocen mejor el producto, pueden tomar nuevas decisiones sobre el proceso de desarrollo y discutir la mejor solución posible para el problema actual. Los desarrolladores se vuelven más profesionales: saben que el trabajo será revisado y están motivados para escribir el código perfecto.

El proceso de revisión de código hace que el desarrollo no solo sea eficiente en la perspectiva a largo plazo, sino que también sea más predecible y permita su estimación. Eso podría lograrse con las limitaciones en la longitud de la línea de texto, el tiempo de revisión, las tasas de defectos aceptables, etc.

En RubyGarage, utilizamos GitHub también como herramienta de comunicación para el proceso de revisión de código. Durante el examen del código de otro desarrollador puede: dejar comentarios, discutir la solución, crear una conversación sobre cualquier parte del código.

También usamos algunos consejos para revisar el código de manera eficiente . Pero antes de enumerarlos, me gustaría destacar que la planificación efectiva da como resultado revisiones de código breves pero efectivas.

La revisión de código es para todo el equipo. Todos los desarrolladores deben familiarizarse con el código (incluso si no lo han escrito) para aprender de los errores de otros y descubrir nuevas técnicas.

Mantenlo corto y dulce. Los desarrolladores dicen que el proceso de revisión del código debe durar aproximadamente una hora, por lo general, tratamos de hacerlo alrededor de 10-30 minutos. Si el proceso de revisión de código lleva más de una hora, es mejor dividirlo en pocas sesiones más pequeñas, porque un proceso duradero generalmente no es tan efectivo.

No revise demasiado código a la vez. En RubyGarage hemos descubierto por nuestra experiencia que revisar alrededor de 200 líneas de código a la vez es la variante más adecuada, porque revisar, por ejemplo, 400 líneas es un desafío terrible para los desarrolladores.

Haga una lista de tareas pendientes. Haga una lista de verificación de todo lo que necesita para no perderse nada durante la revisión del código. Por ejemplo, la seguridad del código, la implementación de la lógica empresarial y los derechos de acceso del usuario son muy importantes para nosotros en RubyGarage, por eso nos concentramos en ellos.

Hazlo sin dolor. Siempre asegúrese de que el código sea lúcido y no requiera comentarios adicionales y contexto de suministro para el revisor. Enviamos nuestro código para su revisión a través de la función Pull Request en GitHub.

Definir la preparación del código. Antes de revisar el código, los desarrolladores deben: escanear el código en busca de errores comunes; verificar la calidad de la revisión automática; verifique que el código corresponda a las guías de estilo relevantes.

Define tu objetivo. Puede concentrarse en la representación del código de la funcionalidad requerida; en evitar el olor del código; sobre la relevancia del código para la guía de estilo. Pero siempre

Cuantifique la efectividad de sus revisiones de código. Puede cuantificar la calidad de su código en función del número de tickets de soporte a lo largo del tiempo; en el número de defectos inyectados por el desarrollo; en marcadores que incluyen la tasa de defectos, la densidad de defectos y la tasa de inspección.

Para obtener información más detallada, puede consultar el artículo ‘¿Qué es la revisión de código y por qué es tan importante?’ https://rubygarage.org/blog/what … y ‘Code Review Tips’ https://rubygarage.org/blog/code

Existe un incentivo financiero para realizar una Revisión del Código Fuente en una aplicación. Una revisión de alta calidad y centrada en la seguridad del código de programación puede reducir significativamente las posibilidades de una violación de la seguridad y sus consiguientes repercusiones financieras. Encontrar y corregir vulnerabilidades de software en el ciclo de desarrollo antes del lanzamiento es mucho menos costoso que corregirlas después de que el código se haya implementado en producción. Además, obtener una Revisión del Código Fuente de una parte externa muestra la debida diligencia por parte de una organización.
Las revisiones de código también brindan beneficios en términos de cumplir con las iniciativas de cumplimiento, especialmente el Estándar de seguridad de datos de la industria de tarjetas de pago (PCI-DSS). La Sección 6.3.7 de PCI-DSS requiere que se revise el código de aplicación personalizado para detectar una posible vulnerabilidad. Para las aplicaciones web orientadas a Internet, una revisión del código fuente es una opción para satisfacer la Sección 6.6.
Para las aplicaciones de pago vendidas por proveedores de software, la Sección 5.1.7 del Estándar de seguridad de datos de aplicaciones de pago (PA-DSS) establece que el código de la aplicación de pago de un proveedor debe revisarse antes de entregarlo a los clientes. El PCIDSS también establece que las aplicaciones web deben desarrollarse de acuerdo con pautas como el Proyecto de seguridad de aplicaciones web abiertas (OWASP).
Uno de los principales beneficios de una revisión del código fuente es la capacidad de tener una visualización del 100% de una aplicación. Las evaluaciones en tiempo de ejecución o las pruebas de penetración de caja negra ciertamente pueden ser valiosas para ayudar a identificar vulnerabilidades, pero a menudo hay algunas dudas sobre si se probaron o no todos los aspectos de la aplicación. Por ejemplo, diferentes configuraciones de configuración podrían exponer u ocultar módulos completos de la aplicación. ¿Qué configuración se usó durante la prueba? O bien, es posible que los evaluadores no hayan utilizado ciertos roles de usuario con diferentes niveles de acceso. Una revisión del código teóricamente puede “ver” todas las diferentes opciones de configuración y roles de usuario.
– Ver más en: Libros blancos
También puede echar un vistazo: Lista de verificación y pautas de revisión de código

Este artículo señala algunas de las áreas comunes en el código C y C ++ que con frecuencia tienen defectos que son fáciles de detectar o necesitan atención especial durante las revisiones. Cuando vea estos patrones en la fuente, debe considerarlos especialmente y verificar que la intención esté codificada correctamente.

De ninguna manera pretende ser una lista exhaustiva de problemas. Use este documento como guía o guía básica para ayudarlo a encontrar problemas comunes y para pensar en defectos adicionales. El objetivo es mejorar sus probabilidades de encontrar defectos en una revisión formal o informal.

Mire el código a continuación: revise el fragmento de código a continuación e intente detectar problemas obvios …

PD: No estoy considerando las pautas de codificación para la nomenclatura de variables, funciones o clases, ya que difieren de una organización a otra / de un equipo a otro. También supongo que este código se compila aunque no he intentado compilarlo. Por si acaso, si hay algún problema de compilación, corrija errores / advertencias.

Alcance de la revisión: no requiere saber qué hace la clase Sample. Por lo tanto, no vaya a la nomenclatura o comentarios sin problemas. ¡Por supuesto, esos son comentarios válidos también!

/ * Fragmento de código pequeño * /

#include
#include
#define SIZE 1073741824

usando el espacio de nombres estándar;

// Este código es intencional

muestra de clase {

privado:

int * buf;

int x;

público:

Muestra (int x)

{

esto -> x = x;

buf = nuevo int [TAMAÑO];

si (0 == esto-> x)

arrojar “Mal valor”;

// Más códigos aquí

}
char * foo () {
char * p = nuevo char [3];
if (! p) devuelve NULL;
para (int i = 0; i <3; ++ i) {
p [i] = x * (i + 1);
}
volver p;
}
~ Muestra () {eliminar [] buf; }

};

Le sugiero que revise el fragmento de código anterior y anote todos sus comentarios para que coincidan con los comentarios de revisión de código de expertos a continuación;). Para mí, sinceramente, el código se ve bien.

¿Tú también lo crees?

Comentarios de Expert Review: OK analicemos el código línea por línea

[1] Uso de macro. Aunque su uso en el contexto correcto es perfectamente correcto e inofensivo, sin embargo, si el revisor hace un comentario para reemplazarlo por algo más sensato, será un comentario válido.

Para aquellos que no saben: las macros no obedecen el alcance de C ++ y las reglas de tipo. Esto es a menudo la causa de problemas sutiles y no tan sutiles. Esta es la razón principal por la que las personas no prefieren usar macro.

[2] Botín en constructor:

Muestra (int x)

(una). buf = nuevo int [TAMAÑO];

Esto asignará TAMAÑO * 4 (1073741824 * 4 = 4294967296) Bytes de memoria en la plataforma de 32 bits. Tenga en cuenta que hay una operación de multiplicación que ocurre dentro de la llamada al nuevo operador. Asegúrese de buscar verificaciones de código, para asegurarse de que sizeof (type) * number-of-objects no se desborde. El código anterior produce un desbordamiento a cero (en una plataforma de 32 bits ya que MAX_INT es 4294967295, que es menor que el tamaño solicitado 4294967296), que es una cantidad de asignación válida, pero que siempre conducirá a un desbordamiento del búfer.

En este punto, “buffer” es un puntero válido, pero que apunta a un trozo de memoria de tamaño cero. Cualquier intento de escritura en el búfer [n] provocará daños en la memoria.

(si). Está bien lanzar una excepción de un constructor siempre que no pueda inicializar (construir) un objeto correctamente. No existe una alternativa realmente satisfactoria para salir de un constructor por medio de un lanzamiento.

Sabemos que ‘nuevo’ puede lanzar una excepción en caso de que falle la asignación de memoria. Entonces escriba manejo de excepciones aquí.

tratar {

nuevo int [TAMAÑO];
}
catch (const std :: bad_alloc & e) {
std :: cout << e.what () << '\ n';
}

(C). Ahora considere el caso cuando la asignación de memoria (buf = new int [SIZE]) tiene éxito. Pero la proxima linea

si (0 == esto-> x)

arrojar “Mal valor”;

lanza una excepción. ¿Lo que sucederá? – el objeto no se ha iniciado correctamente, por lo que no se llamaría a ningún destructor para eliminar la memoria asignada en “buf”. Y, por lo tanto, resulta “pérdida de memoria”.

Como arreglarlo ? – Una solución es utilizar “punteros inteligentes”.

auto_ptr ahora se ha vuelto obsoleto (C ++ estándar 11), por lo que sugeriría en este caso usar unique_ptr.

(Lea más sobre los punteros inteligentes para comprender por qué estoy haciendo este punto aquí)

[3]

Luego viene el método “foo”

(una).

Mira el código a continuación

char * p = nuevo char [3];

if (! p) devuelve NULL;

Referir

punto [2] (b), si la asignación de memoria falla, los nuevos lanzan una excepción (bad_alloc). Ah, sí, pero hemos escrito la siguiente línea (if (! P) return NULL) para devolver NULL en ese caso.

Por supuesto, usted ha escrito, pero ¿puede decirme, si nuevo arrojará una excepción, su verificación de seguridad (si (! P) devuelve NULL) incluso se ejecuta? – No, porque el control se irá.

Entonces, el primer comentario de revisión es que ha escrito una línea que nunca se ejecutará en el caso de que quisiera que se ejecutara.

Como arreglarlo ?

Dos formas: Primero, use el manejo de excepciones. O, en segundo lugar, si aún desea utilizar el concepto de verificación de seguridad, utilice

nuevo (std :: nothrow)

en lugar de nuevo Sí, al usar new (std :: nothrow), en caso de que falle la asignación de memoria,

new devolverá el puntero NULL y no lanzará una excepción.

Me gustaría probar: ejecutar el siguiente código de prueba en un proyecto de prueba C ++

#include
#include

int main ()
{
tratar {
while (verdadero) {
nuevo int [100000000ul]; // lanzar sobrecarga
}
} catch (const std :: bad_alloc & e) {
std :: cout << e.what () << '\ n';
}

while (verdadero) {
int * p = nuevo (std :: nothrow) int [100000000ul]; // sobrecarga no arrojadiza
if (p == NULL) {

std :: cout << "Asignación devuelta nullptr \ n";

rotura;

}

}

}

salida:

std :: bad_alloc

Asignación devuelta nullptr

(si). El último problema, puedo ver aquí, es que la función regresa

char *. En realidad, el problema no es devolver char *, el problema es devolver “memoria de montón” (memoria asignada usando new).

Los consumidores del método “foo” no tienen idea de si está devolviendo “memoria de montón” o “segmento de datos” o “pila”. Por lo tanto, el consumidor nunca se preocupará por la limpieza de la memoria, lo que podría conducir a una “pérdida de memoria”. Por lo tanto, sus responsabilidades de desarrollador de “Muestra” para “cuidar de su propio recurso”.

Como arreglarlo ? Mediante el uso de “string” o smart_pointers como “unique_ptr”.

Arreglemos todos los comentarios mencionados anteriormente y su código se verá (mucho mejor):

/ * Fragmento de código pequeño * /

#include
#include
#include
#define SIZE 1073741823 // Reduce el tamaño en 1, por ejemplo, o hace otras cosas
// depende del requisito de trabajo
// Como alternativa a la macro, puede usar const int

usando el espacio de nombres estándar; // Este código es intencional

muestra de clase {

privado:
unique_ptr buf; // Usando el puntero inteligente aquí, resolvió todos los problemas
// discutido en el punto [2].
int x;
público:
Muestra (int x): buf (nuevo int [TAMAÑO])
{
esto-> x = x;
si (0 == esto-> x)
arrojar “Mal valor”;
// Más códigos aquí
}
unique_ptr foo () {
unique_ptr
p (nuevo char [3]); // Alternativa podría estar usando
// cuerda
if (! p) devuelve NULL;
para (int i = 0; i <3; ++ i) {
p [i] = ‘a’;
}
volver p;
}
~ Sample () {} // puntero inteligente se encargará de su eliminación
};

Bueno, otro Quoran te ha dado mucho para leer, así que me detendré y te daré una respuesta breve.

La revisión o inspección de código es una buena práctica al principio y puede eliminar muchos errores, errores de formato, desbordamientos de búfer, pérdidas de memoria, etc.
Estas son algunas vulnerabilidades comunes que pueden causar fallas leves en su software.

tome por ejemplo que ingrese un número ‘largo’ en un cuadro de texto ‘int’ y lo envíe.
Ahora óptimamente debería haber dos protecciones aquí
Límite de caracteres basado en JS y verificación de tipo.
Código basado en back-end para dar un mensaje de error y no enviar los datos.

Ahora puede haber dado solo uno de estos y, por lo tanto, puede causar dos tipos de errores.

Si tiene una protección basada en back-end aquí, uno puede decir por qué incluso permite que el usuario envíe el formulario

Mientras que si tiene una protección basada en JS, es posible que JS esté deshabilitado y el usuario envíe un valor incorrecto que puede causar o no estos:
1) error o diferentes tipos
2. truncamiento de valores

Ambos son dañinos.

Por eso es importante la revisión del código.

el software no es solo un programa sino que está muy por encima de lo que llamamos programa. No es posible volver a escribir el código desde cero. La industria SO prefiere la revisión del código. Revisión del código en cada unidad funcional del módulo.