Complejidad ciclomática cuando se llama al mismo método varias veces

12

Gracias a una pregunta en Code Review , tuve un pequeño desacuerdo (que esencialmente es una oportunidad para aprender algo) sobre qué es exactamente la Complejidad Ciclomática para el siguiente código.

public static void main(String[] args) {
    try {
        thro();
        thro();
        thro();
        thro();
        thro();
        thro();
        thro();
    }
    catch (NullPointerException e) {
    }
}

private static Random random = new Random();

public static void thro() throws NullPointerException {
    if (random.nextBoolean())
        throw new NullPointerException();
    System.out.println("No crash this time");
}

Al escribir este código en Eclipse y usar el complemento de métricas Eclipse , me dice que la Complejidad Ciclomática McCabe para el método principal es 2, y para el thrométodo dice 2.

Sin embargo, alguien más me dice que la complejidad de llamar throvarias veces es number of calls * method complexityy, por lo tanto, afirma que la complejidad del método principal es 7 * 2 = 14.

¿Estamos midiendo cosas diferentes? ¿Podemos los dos estar en lo correcto? ¿O cuál es la complejidad ciclomática real aquí?

Simon Forsberg
fuente
55
El CC de la función es dos, ya que solo hay dos rutas. El CC del programa es más alto. Esta es una puñalada completa en la oscuridad, pero supongo que el software de análisis de código toma cada función como una caja negra separada debido a la imposibilidad de calcular el CC de una aplicación compleja completa de una sola vez.
Phoshi
@Phoshi Si escribe eso como respuesta y (si es posible) proporciona enlaces que muestren que hay una separación de los dos, con mucho gusto acepto esa respuesta.
Simon Forsberg
Si cuenta todas las rutas causadas por posibles excepciones en las mediciones de CC, Dios ayude al tipo que hizo la pregunta sobre la refactorización de un código trivial para obtener el número por debajo de 10.
mattnz 05 de

Respuestas:

9

Cuando entendí esto correctamente, la Complejidad Ciclomática de maines 8, que es el número de caminos linealmente independientes a través del código. O obtienes una excepción en una de las siete líneas, o ninguna, pero nunca más de una. Cada uno de esos posibles "puntos de excepción" corresponde exactamente a una ruta diferente a través del código.

Supongo que cuando McCabe inventó esa métrica, no tenía lenguajes de programación con el manejo de excepciones en mente.

Doc Brown
fuente
Pero, ¿realmente importa cuál de las líneas arroja la excepción?
Simon Forsberg el
55
@ SimonAndréForsberg: sí, lo hace. Piense en "thro" que tiene un efecto secundario donde incrementa un contador global cuando se lo llama (eso no cambiaría las posibles rutas a través del código). Los posibles resultados de ese contador son de 0 a 7, por lo que esto prueba que el CC es al menos 8.
Doc Brown
¿Diría que el complemento de métricas que estoy utilizando informa un valor incorrecto para el mainmétodo?
Simon Forsberg el
@ SimonAndréForsberg: bueno, no conozco tu plugin de métricas, pero obviamente 2 no es 8.
Doc Brown
Hay un enlace al complemento de métricas en mi pregunta ...
Simon Forsberg
6

Siendo 'el otro tipo', responderé aquí, y seré preciso sobre lo que digo (con lo que no fui particularmente preciso con respecto a otros formularios).

Usando el ejemplo de código anterior, calculo la complejidad ciclomática como 8, y tengo comentarios en el código para mostrar cómo calculo eso. Para describir las rutas, consideraré un bucle exitoso a través de todas las thro()llamadas como la ruta de código 'principal' (o 'CP = 1'):

public static void main(String[] args) {
  try {
             // This is the 'main' Code Path: CP = 1
    thro();  // this has a branch, can succeed CP=1 or throw CP=2
    thro();  // this has a branch, can succeed CP=1 or throw CP=3
    thro();  // this has a branch, can succeed CP=1 or throw CP=4
    thro();  // this has a branch, can succeed CP=1 or throw CP=5
    thro();  // this has a branch, can succeed CP=1 or throw CP=6
    thro();  // this has a branch, can succeed CP=1 or throw CP=7
    thro();  // this has a branch, can succeed CP=1 or throw CP=8
  }
  catch (NullPointerException e) {
  }
}

Entonces, cuento 8 rutas de código en este método principal, que para mí es una Complejidad Ciclomática de 8.

En términos de Java, cada mecanismo para salir de una función cuenta para su complejidad, por lo tanto, un método que tiene un estado de éxito y, por ejemplo, arroja, posiblemente, hasta 3 excepciones, tiene 4 rutas de salida documentadas.

La complejidad de un método que llama a tal función es:

CC(method) = 1 + sum (methodCallComplexity - 1)

Creo que otras cosas a considerar es que, en mi opinión, la catchcláusula no contribuye a la complejidad del método, catches simplemente el objetivo de una throwsrama y, por lo tanto, un bloque de captura que es el objetivo de múltiples throwconteos 1 vez para cada uno throw, y no solo una vez para todo.

rolfl
fuente
¿Está contando también las posibles ramas para OutOfMemoryExceptions? Quiero decir, pedantemente, pueden causar ramificaciones de código, pero nadie las cuenta ya que diluyen la utilidad de la métrica.
Telastyn
No, no lo estoy ... y tienes razón, pero, en el contexto de este argumento, cuento solo las excepciones que se declara que arroja el método. Además, si un método declara tres excepciones, pero el código callinch sí catch (Throwable t) {..., supongo que no importa cuántas excepciones declare lanzar.
rolfl