Declaración de cambio con devoluciones: corrección del código


91

Digamos que tengo código en C con aproximadamente esta estructura:

switch (something)
{
    case 0:
      return "blah";
      break;

    case 1:
    case 4:
      return "foo";
      break;

    case 2:
    case 3:
      return "bar";
      break;

    default:
      return "foobar";
      break;
}

Ahora, obviamente, los breaks no son necesarios para que el código se ejecute correctamente, pero parece una mala práctica si no se los pongo allí.

¿Qué piensas? ¿Está bien eliminarlos? ¿O los conservaría para una mayor "corrección"?

Respuestas:


137

Quite las breakdeclaraciones. No son necesarios y quizás algunos compiladores emitan advertencias de "Código inalcanzable" .


2
Lo mismo se aplica con otras declaraciones de salto de control incondicionales, como continueo goto- es idiomático usarlas en lugar de break.
caf

32

Tomaría un rumbo completamente diferente. No REGRESES en medio del método / función. En su lugar, simplemente ponga el valor de retorno en una variable local y envíelo al final.

Personalmente, considero que lo siguiente es más legible:

String result = "";

switch (something) {
case 0:
  result = "blah";
  break;
case 1:
  result = "foo";
  break;
}

return result;

24
La filosofía "One Exit" tiene sentido en C, donde debe asegurarse de que las cosas se limpien correctamente. Teniendo en cuenta que en C ++ puede ser retirado de la función en cualquier momento por una excepción, la filosofía de salida única no es realmente útil en C ++.
Billy ONeal

29
En teoría, esta es una gran idea, pero con frecuencia requiere bloques de control adicionales que pueden dificultar la legibilidad.
Mikerobi

8
La razón principal por la que hago las cosas de esta manera es que en funciones más largas es muy fácil pasar por alto el vector de salida al escanear el código. Sin embargo, cuando la salida está siempre al final, es más fácil comprender rápidamente lo que está sucediendo.
NotMe

7
Bueno, eso y los bloques de retorno en medio del código me recuerdan el abuso de GOTO.
NotMe

5
@Chris: En funciones más largas, estoy totalmente de acuerdo, pero eso generalmente habla de problemas más grandes, así que refactorícelo. En muchos casos, es una función trivial regresar en un interruptor, y está muy claro lo que se supone que debe suceder, así que no desperdicie el poder del cerebro rastreando una variable adicional.
Stephen

8

Personalmente eliminaría las devoluciones y me quedaría con las pausas. Usaría la declaración de cambio para asignar un valor a una variable. Luego devuelve esa variable después de la declaración de cambio.

Aunque este es un punto discutible, siempre he sentido que un buen diseño y encapsulación significan una entrada y una salida. Es mucho más fácil garantizar la lógica y no perder accidentalmente el código de limpieza basado en la complejidad ciclomática de su función.

Una excepción: devolver antes de tiempo está bien si se detecta un parámetro incorrecto al comienzo de una función, antes de que se adquieran los recursos.


Puede ser bueno para este particular switch, pero no necesariamente el mejor en general. Bueno, digamos que la declaración de cambio dentro de una función precede a otras 500 líneas de código que son válidas solo cuando ciertos casos lo son true. ¿Cuál es el punto de ejecutar todo ese código extra para los casos que no necesitan ejecutarlo? ¿No es mejor estar returndentro del switchpara esos cases?
Viernes

6

Mantenga las pausas: es menos probable que tenga problemas si / cuando edita el código más tarde si las pausas ya están en su lugar.

Habiendo dicho eso, muchos (incluyéndome a mí) lo consideran una mala práctica regresar de la mitad de una función. Idealmente, una función debería tener un punto de entrada y un punto de salida.


5

Retirarlos. Es idiomático regresar de casedeclaraciones, y es ruido de "código inalcanzable" de otra manera.


5

Yo los quitaría. En mi libro, un código muerto como ese debería considerarse un error porque te hace pensar dos veces y preguntarte "¿Cómo podría ejecutar esa línea?"


4

Normalmente escribiría el código sin ellos. En mi opinión, el código muerto tiende a indicar descuido y / o falta de comprensión.

Por supuesto, también consideraría algo como:

char const *rets[] = {"blah", "foo", "bar"};

return rets[something];

Editar: incluso con la publicación editada, esta idea general puede funcionar bien:

char const *rets[] = { "blah", "foo", "bar", "bar", "foo"};

if ((unsigned)something < 5)
    return rets[something]
return "foobar";

En algún momento, especialmente si los valores de entrada son escasos (por ejemplo, 1, 100, 1000 y 10000), en su lugar, desea una matriz dispersa. Puede implementar eso como un árbol o un mapa razonablemente bien (aunque, por supuesto, un interruptor también funciona en este caso).


Editó la publicación para reflejar por qué esta solución no funcionaría bien.
houbysoft

@tu edición: Sí, pero se necesitaría más memoria, etc. En mi humilde opinión, el cambio es la forma más sencilla, que es lo que quiero en una función tan pequeña (solo hace el cambio).
houbysoft

3
@houbysoft: mire detenidamente antes de concluir que se necesitará memoria extra. Con un compilador típico, usar "foo" dos veces (por ejemplo) usará dos punteros a un solo bloque de datos, no replicará los datos. También puede esperar un código más corto. A menos que tenga muchos valores repetidos, a menudo ahorrará memoria en general.
Jerry Coffin

cierto. Sin embargo, seguiré con mi solución porque la lista de valores es muy larga y un cambio parece mejor.
houbysoft

1
De hecho, la respuesta más elegante y eficiente a un problema como este, cuando los valores del conmutador son contiguos y el tipo de datos es homogéneo, es utilizar una matriz para evitar un cambio por completo.
Dwayne Robinson

2

Yo diría que los elimine y defina un valor predeterminado: branch.


Si no hay un valor predeterminado razonable, no debería definir un valor predeterminado.
Billy ONeal

hay uno, y definí uno, simplemente no lo puse en la pregunta, lo edité ahora.
houbysoft

2

¿No sería mejor tener una matriz con

arr[0] = "blah"
arr[1] = "foo"
arr[2] = "bar"

y hacer return arr[something];?

Si se trata de la práctica en general, debe mantener las breakdeclaraciones en el conmutador. En el caso de que no necesite returndeclaraciones en el futuro, disminuye la posibilidad de que pasen a la siguiente case.


Editó la publicación para reflejar por qué esta solución no funcionaría bien.
houbysoft

2

Para "corrección", los bloques de entrada única y salida única son una buena idea. Al menos lo estaban cuando hice mi licenciatura en informática. Entonces probablemente declararía una variable, la asignaría en el interruptor y regresaría una vez al final de la función


2

¿Qué piensas? ¿Está bien eliminarlos? ¿O los conservaría para una mayor "corrección"?

Está bien eliminarlos. El uso return es exactamente el escenario donde breakno se debe usar.


1

Interesante. El consenso de la mayoría de estas respuestas parece ser que la breakdeclaración redundante es un desorden innecesario. Por otro lado, leí la breakdeclaración en un interruptor como el 'cierre' de un caso. caselos bloques que no terminan en un breaktienden a saltar hacia mí como una caída potencial a través de errores.

Sé que no es así cuando hay un en returnlugar de un break, pero así es como mis ojos 'leen' los bloques de la caja en un interruptor, por lo que personalmente preferiría que cada uno caseesté emparejado con un break. Pero muchos compiladores se quejan de que el breakafter returnes superfluo / inalcanzable y, aparentemente, parece que estoy en la minoría de todos modos.

Así que deshazte del breaksiguiente a return.

NB: todo esto es ignorar si violar la regla de entrada / salida única es una buena idea o no. En cuanto a eso, tengo una opinión que lamentablemente cambia según las circunstancias ...


0

Yo digo que los elimines. Si su código es tan ilegible que necesita colocar interrupciones allí 'para estar seguro', debe reconsiderar su estilo de codificación :)

Además, siempre he preferido no mezclar pausas y devoluciones en la declaración de cambio, sino seguir con uno de ellos.


0

Yo personalmente tiendo a perder los breaks. Posiblemente, una fuente de este hábito sea la programación de procedimientos de ventana para aplicaciones de Windows:

LRESULT WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_SIZE:
            return sizeHandler (...);
        case WM_DESTROY:
            return destroyHandler (...);
        ...
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Personalmente, encuentro este enfoque mucho más simple, conciso y flexible que declarar una variable de retorno establecida por cada controlador y luego devolverla al final. Dado este enfoque, los breaks son redundantes y, por lo tanto, deberían funcionar; no tienen ningún propósito útil (sintácticamente o en mi opinión, visualmente) y solo hinchan el código.


0

Creo que los * break * s están ahí con un propósito. Es mantener viva la "ideología" de la programación. Si simplemente "programamos" nuestro código sin coherencia lógica, tal vez lo pueda leer ahora, pero intente mañana. Intente explicárselo a su jefe. Intente ejecutarlo en Windows 3030.

Bleah, la idea es muy simple:


Switch ( Algorithm )
{

 case 1:
 {
   Call_911;
   Jump;
 }**break**;
 case 2:
 {
   Call Samantha_28;
   Forget;
 }**break**;
 case 3:
 {
   Call it_a_day;
 }**break**;

Return thinkAboutIt?1:return 0;

void Samantha_28(int oBed)
{
   LONG way_from_right;
   SHORT Forget_is_my_job;
   LONG JMP_is_for_assembly;
   LONG assembly_I_work_for_cops;

   BOOL allOfTheAbove;

   int Elligence_says_anyways_thinkAboutIt_**break**_if_we_code_like_this_we_d_be_monkeys;

}
// Sometimes Programming is supposed to convey the meaning and the essence of the task at hand. It is // there to serve a purpose and to keep it alive. While you are not looking, your program is doing  // its thing. Do you trust it?
// This is how you can...
// ----------
// **Break**; Please, take a **Break**;

/ * Solo una pregunta menor. ¿Cuánto café ha tomado mientras leía lo anterior? A veces rompe el sistema * /


0

Código de salida en un punto. Eso proporciona una mejor legibilidad del código. Agregar declaraciones de retorno (salidas múltiples) entre medias dificultará la depuración.


0

Si tiene un tipo de código de "búsqueda", puede empaquetar la cláusula switch-case en un método por sí mismo.

Tengo algunos de estos en un sistema de "pasatiempos" que estoy desarrollando por diversión:

private int basePerCapitaIncomeRaw(int tl) {
    switch (tl) {
        case 0:     return 7500;
        case 1:     return 7800;
        case 2:     return 8100;
        case 3:     return 8400;
        case 4:     return 9600;
        case 5:     return 13000;
        case 6:     return 19000;
        case 7:     return 25000;
        case 8:     return 31000;
        case 9:     return 43000;
        case 10:    return 67000;
        case 11:    return 97000;
        default:    return 130000;
    }
}

(Sí. Ese es el espacio GURPS ...)

Estoy de acuerdo con otros en que, en la mayoría de los casos, debería evitar más de una devolución en un método, y reconozco que este podría haberse implementado mejor como una matriz o algo más. Acabo de descubrir que switch-case-return es una coincidencia bastante fácil con una tabla de búsqueda con una correlación de 1-1 entre entrada y salida, como lo anterior (los juegos de rol están llenos de ellos, estoy seguro de que existen en otros "empresas" también): D

Por otro lado, si la cláusula case es más compleja, o si algo sucede después de la instrucción switch, no recomendaría usar return en ella, sino establecer una variable en el switch, finalizarla con una interrupción y regresar el valor de la variable al final.

(En la ... tercera? Mano ... siempre se puede refactorizar un conmutador en su propio método ... dudo que tenga un efecto en el rendimiento, y no me sorprendería que los compiladores modernos pudieran reconocerlo como algo que podría estar en línea ...)

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.