¿Cuándo es apropiado hacer una función separada cuando solo habrá una sola llamada a dicha función? [duplicar]

43

Estamos diseñando estándares de codificación, y estamos teniendo desacuerdos en cuanto a si alguna vez es apropiado dividir el código en funciones separadas dentro de una clase, cuando esas funciones solo se llamarán una vez.

Por ejemplo:

f1()
{
   f2();  
   f4();
}


f2()
{
    f3()
    // Logic here
}


f3()
{
   // Logic here
}

f4()
{
   // Logic here
}

versus:

f1()
{
   // Logic here
   // Logic here
   // Logic here
}

Algunos argumentan que es más fácil de leer cuando se divide una función grande utilizando sub funciones separadas de un solo uso. Sin embargo, al leer el código por primera vez, me resulta tedioso seguir las cadenas lógicas y optimizar el sistema en su totalidad. ¿Hay alguna regla que se aplique normalmente a este tipo de diseño de función?

Tenga en cuenta que, a diferencia de otras preguntas, solicito el mejor conjunto de condiciones para diferenciar los usos permitidos y no permitidos de las funciones de llamada única, no solo si están permitidas.

    
pregunta David 23.01.2016 - 01:05

8 respuestas

88

La razón detrás de las funciones de división no es la cantidad de veces que se llamarán , sino que las mantiene pequeñas y evita que hagan varias cosas diferentes.

libro de Bob Martin Clean Code proporciona buenas pautas sobre cuándo dividir una función:

  
  • Las funciones deben ser pequeñas; ¿cuán pequeño? Vea la bala abajo.
  •   
  • Las funciones deben hacer solo una cosa.
  •   

Entonces, si la función es de varias pantallas, divídala. Si la función hace varias cosas, divídala.

Si la función está formada por pasos secuenciales dirigidos a un resultado final, no es necesario dividirlo, incluso si es relativamente largo. Pero si las funciones hacen una cosa, luego otra, luego otra y luego otra, con condiciones, bloques separados lógicamente, etc., deberían dividirse. Como resultado de esa lógica, las funciones deben ser generalmente pequeñas.

Si f1() realiza la autenticación, f2() analiza la entrada en partes más pequeñas, si f3() realiza cálculos, y f4() registra o persiste los resultados, entonces obviamente deben separarse, incluso cuando cada uno de ellos sea Llamado solo una vez.

De esta forma, puede refactorizarlos y probarlos por separado, además de la ventaja adicional de ser más fácil de leer .

Por otra parte, si toda la función lo hace es:

a=a+1;
a=a/2;
a=a^2
b=0.0001;
c=a*b/c;
return c;

entonces no hay necesidad de dividirlo, incluso cuando la secuencia de pasos es larga.

    
respondido por el Tulains Córdova 23.01.2016 - 01:19
43

Creo que la denominación de funciones es muy importante aquí.

Una función muy diseccionada puede ser muy autodocumentada. Si cada proceso lógico dentro de una función se divide en su propia función, con una lógica interna mínima, el comportamiento de cada declaración puede ser razonado por los nombres de las funciones y los parámetros que toman.

Por supuesto, hay un inconveniente: los nombres de las funciones. Al igual que los comentarios, estas funciones altamente específicas a menudo pueden desincronizarse con lo que realmente está haciendo la función. Pero , al mismo tiempo, al darle un nombre de función adecuado, hace que sea más difícil para justificar el alcance del alcance. Se vuelve más difícil hacer que una subfunción haga algo más de lo que claramente debería hacer.

Por lo que sugeriría esto. Si cree que una sección del código podría dividirse, aunque nadie más lo llame, hágase esta pregunta: "¿Qué nombre le daría?"

Si responder a esa pregunta le lleva más de 5 segundos, o si el nombre de la función que elija es decididamente opaco, es muy probable que no sea una unidad lógica separada dentro de la función. O al menos, que no está lo suficientemente seguro acerca de lo que realmente está haciendo esa unidad lógica para dividirla adecuadamente.

Pero hay un problema adicional que pueden encontrar las funciones muy diseccionadas: corrección de errores.

Es difícil rastrear errores lógicos dentro de una función de más de 200 líneas. ¿Pero rastrearlas a través de más de 10 funciones individuales, mientras intentas recordar las relaciones entre ellas? Eso puede ser aún más difícil.

Sin embargo, de nuevo, la auto-documentación semántica a través de los nombres puede jugar un papel clave. Si cada función tiene un nombre lógico, entonces todo lo que necesita hacer para validar una de las funciones de hoja (además de la prueba de unidad) es ver si realmente hace lo que dice que hace. Las funciones de las hojas tienden a ser cortas y enfocadas. Entonces, si cada función individual de la hoja hace lo que dice que debería, entonces el único problema posible es que alguien les haya pasado las cosas incorrectas.

Entonces, en ese caso, puede ser una ventaja para la corrección de errores.

Creo que realmente se reduce a la cuestión de si se puede asignar un nombre significativo a una unidad lógica. Si es así, entonces probablemente pueda ser una función.

    
respondido por el Nicol Bolas 23.01.2016 - 01:31
12

Cada vez que sientas la necesidad de escribir un comentario para describir lo que hace un bloque de texto, has encontrado la oportunidad de extraer un método.

En lugar de

//find eligible contestants
var eligible = contestants.Where(c=>c.Age >= 18)
eligible = eligible.Where(c=>c.Country == US)

prueba

var eligible = FindEligible(contestants)
    
respondido por el dss539 23.01.2016 - 13:34
5

DRY: no se repita, es solo uno de los varios principios que deben ser equilibrados.

Algunos otros que vienen a la mente aquí son los nombres. Si la lógica es complicada no es obvia para el lector ocasional, la extracción en el método / función cuyo nombre encapsula mejor qué y por qué lo está haciendo puede mejorar la legibilidad del programa.

También puede entrar en juego el objetivo de menos de 5-10 líneas de método / función de código, dependiendo de cuántas líneas se convierta en //logic .

Además, una función con parámetros puede actuar como una api y puede nombrar los parámetros de manera adecuada para luego aclarar la lógica del código.

También es posible que con el tiempo las colecciones de tales funciones revelen una agrupación útil del domo, por ejemplo. admin y luego se pueden reunir fácilmente debajo de él.

    
respondido por el Michael Durrant 23.01.2016 - 01:33
4

El punto sobre la división de funciones tiene que ver con una cosa: la simplicidad.

Un lector de código no puede tener en mente más de siete cosas simultáneamente. Tus funciones deben reflejar eso.

  • Si construyes funciones demasiado largas, serán ilegibles porque tienes muchas más cosas dentro de tus funciones.

  • Si creas una tonelada de funciones de una línea, los lectores también se confunden en la maraña de funciones. Ahora tendría que tener más de siete funciones en su memoria para comprender el propósito de cada una.

  • Algunas funciones son simples aunque son largas. El ejemplo clásico es cuando una función contiene una instrucción de cambio grande con muchos casos. Mientras el manejo de cada caso sea simple, sus funciones no son demasiado largas.

Ambos extremos ( Megamoth y la sopa de funciones pequeñas) son igualmente malos, y tienes que lograr un equilibrio en el medio. En mi experiencia, una buena función tiene algo alrededor de diez líneas. Algunas funciones serán de una sola línea, otras superarán las veinte líneas. Lo importante es que cada función cumple una función fácilmente comprensible y, al mismo tiempo, es igualmente comprensible en su implementación.

    
respondido por el cmaster 23.01.2016 - 21:58
2

Depende mucho de lo que sea // Logic Here .

Si se trata de una sola línea, entonces probablemente no necesite una descomposición funcional.

Si, por otro lado, son líneas y líneas de código, entonces es mucho mejor ponerlo en una función separada y nombrarlo adecuadamente ( f1,f2,f3 no pasa el examen aquí).

Todo esto tiene que ver con que los cerebros humanos en promedio no son muy eficientes con el procesamiento de grandes cantidades de datos de un vistazo. En cierto sentido, no importa qué datos: función multilínea, intersección ocupada, rompecabezas de 1000 piezas.

Sea un amigo del cerebro del mantenedor de su código, corte pedazos en el punto de entrada. Quién sabe, ese mantenedor de código puede incluso ser usted un mes después.

    
respondido por el Alexander Pogrebnyak 23.01.2016 - 03:28
2

Se trata de separación de preocupaciones . (ok, no todos al respecto; esto es una simplificación).

Esto está bien:

function initializeUser(name, job, bye) {
    this.username = name;
    this.occupation = job;
    this.farewell = bye;
    this.gender = Gender.unspecified;
    this.species = Species.getSpeciesFromJob(this.occupation);
    ... etc in the same vein.
}

Esa función se ocupa de una sola preocupación: establece las propiedades iniciales de un usuario a partir de los argumentos proporcionados, los valores predeterminados, la extrapolación, etc.

Esto no está bien:

function initializeUser(name, job, bye) {
    // Connect to internet if not already connected.
    modem.getInstance().ensureConnected();
    // Connect to user database
    userDb = connectToDb(USER_DB);
    // Validate that user does not yet exist.
    if (1 != userDb.db_exec("SELECT COUNT(*) FROM 'users' where 'name' = %d", name)) {
        throw new sadFace("User exists");
    }
    // Configure properties. Don't try to translate names.
    this.username = removeBadWords(name);
    this.occupation = removeBadWords(translate(name));
    this.farewell = removeBadWords(translate(name));
    this.gender = Gender.unspecified;
    this.species = Species.getSpeciesFromJob(this.occupation);
    userDb.db_exec("INSERT INTO 'users' set 'name' = %s", this.username);
    // Disconnect from the DB.
    userDb.disconnect();
}

Separación de inquietudes sugiere que esto debe manejarse como múltiples inquietudes; El manejo de la base de datos, la validación de la existencia del usuario, la configuración de las propiedades. Cada uno de estos se prueba fácilmente como una unidad, pero al probarlos todos en un solo método se obtiene un conjunto de prueba de unidad muy complicado, que probaría cosas tan diferentes como la forma en que manejó el DB y cómo se maneja la creación de títulos de trabajo vacíos e inválidos, y cómo maneja la creación de un usuario dos veces (respuesta: mal, hay un error).

Parte del problema es que va por todas partes en términos de cuán alto es su nivel: las redes de bajo nivel y las cosas de DB no tienen lugar aquí. Eso es parte de la separación de la preocupación. La otra parte es que las cosas que deberían ser la preocupación de otra cosa, es la preocupación de la función init. Por ejemplo, si traducir o aplicar filtros de lenguaje incorrecto, podría tener más sentido como una preocupación de los campos que se están configurando.

    
respondido por el Dewi Morgan 25.01.2016 - 01:45
1

La respuesta "correcta", de acuerdo con los dogmas de codificación prevalentes, es dividir las funciones grandes en funciones pequeñas, fáciles de leer, verificables y probadas con nombres de auto-documentación.

Dicho esto, definir "grande" en términos de "líneas de código" puede parecer arbitrario, dogmático y tedioso, lo que puede causar desacuerdos, escrúpulos y tensión innecesarios. Pero, no temas! Porque, si reconocemos que los propósitos principales detrás del límite de líneas de código son la legibilidad y la capacidad de prueba, ¡podemos encontrar fácilmente el límite de línea apropiado para cualquier función dada! (Y comience a construir la base para un límite permanente relevante )

Estar de acuerdo en equipo para permitir las funciones megalíticas y extraer líneas en funciones más pequeñas y bien nombradas en el signo first de que la función es difícil de leer en su totalidad, o cuando los subconjuntos son inciertos en la corrección.

... Y si todos son honestos durante la implementación inicial, y si ninguno de ellos promociona un coeficiente intelectual superior a 200, los límites de comprensibilidad y comprobabilidad a menudo se pueden identificar antes de que alguien más vea su código.

    
respondido por el svidgen 23.01.2016 - 06:39

Lea otras preguntas en las etiquetas