Mi profesor de informática me dice que debería hacer solo una cosa por función. ¿Cuál es la práctica aceptada con respecto a cuánto deben hacer las funciones de trabajo?

Su instructor de informática le está enseñando a qué se refiere la industria como las mejores prácticas. La mejor práctica es codificar un método o función para realizar una tarea. Esto puede parecerle algo tonto en este momento, ya que se encuentra codificando funciones de una línea. Sin embargo, a medida que comience a codificar programas más complejos, encontrará que la mayoría de las tareas consisten en varios pasos. Como tal, la mayoría de sus funciones contendrán varias líneas de código.

Considere codificar una función para encontrar las raíces de una ecuación cuadrática.

Esta es una tarea única que consta de muchas subtareas.

Aquí hay un ejemplo parcialmente completado. En este caso, codificaría una función para cada paso, 1- 4 y 1a, 1b y 3a. Probablemente agregaré más funciones a medida que codifique, desglosando tareas más pequeñas como las veo.

1. Lea los valores para a, by c.

a. Leer entrada

si. Validar entrada (Aquí también mostraría el error. Con la pantalla ya es una función).

2. Maneje el caso de a = 0.

3. Resuelve la ecuación

a. Calcule el valor o el valor debajo de la raíz cuadrada por separado para asegurarse de que no sea imaginario.

4. Visualice o imprima la solución.

Tenga en cuenta que el paso 1 es una tarea única con dos subtareas. En este caso, comenzaría codificando una sola función como se muestra con el PSEUDOCODE a continuación.

/ * Recuperar y validar la entrada para la ecuación cuadrática * /

retrieveInput () {

número a, b, c;

temperatura del carbón;

/ * Recuperar y validar un * /

mientras que (a es nulo) {

pantalla (“Ingrese un”);

temp = leer ();

if (validar (temp)) {

a = temp;

}

más {

temp = nulo;

}

}

/ * Recuperar y validar b * /

mientras que (b es nulo) {

pantalla (“Ingrese un”);

temp = leer ();

if (validar (temp)) {

b = temp;

}

más {

temp = nulo;

}

}

/ * Recuperar y validar c * /

mientras que (c es nulo) {

pantalla (“Ingrese un”);

temp = leer ();

if (validar (temp)) {

c = temp;

}

más {

temp = nulo;

}

}

}

¿Verse bien? Bueno, es un poco difícil de leer. Además, veo tres bloques de código esencialmente duplicado. Tener un código duplicado hace que un programa sea difícil de leer y mantener. Quizás lo siguiente funcionaría un poco mejor.

/ * Recuperar y validar la entrada para la ecuación cuadrática * /

retrieveInput () {

número a, b, c;

a = retrieveValue (“a”);

b = retrieveValue (“b”);

c = retrieveValue (“c”);

}

/ * Recuperar y validar un solo valor numérico * /

retrieveValue (valor) {

temp de char = nulo;

número retValue = nulo;

while (temp es nulo) {

pantalla (“Ingrese” + valor);

temp = leer ();

if (validar (temp)) {

retValue = temp;

} más {

temp = nulo;

}

}

regreso

}

¿No es ahora mucho más fácil ver lo que la función retrieveInput está tratando de lograr?

Esto todavía necesita bastante trabajo y hay tantas formas de hacerlo como desarrolladores. Sin embargo, creo que le da una idea de por qué su instructor le dice que una función debe lograr solo una cosa y por qué esto se considera la mejor práctica.

también conocido como KISS – Keep It Simple Stupid

Sus compañeros desarrolladores lo apreciarán.

PD Perdón por la falta de espacio en el código. La pantalla de Quora me permitiría sangrar solo hasta ahora.

El número óptimo de funciones es un equilibrio entre dos preocupaciones de diseño:

  1. Si sus funciones son demasiado grandes, entonces el código dentro del cuerpo de la función se vuelve difícil de entender y difícil de verificar. Se puede acceder a las variables introducidas en una parte del cuerpo de la función desde otras partes del cuerpo de la función, lo que significa que hay información de estado innecesaria que debe tener en cuenta al verificar que la función funciona según lo previsto. Si prueba la función y resulta que no funciona, entonces es difícil determinar la ubicación exacta de la falla.
  2. Por otro lado, si sus funciones son muy pequeñas, introducirá muchas definiciones de funciones nuevas; muchas de estas definiciones de funciones pueden no tener ningún significado para el programa en general y puede ser difícil detectar qué funciones se supone que deben usar otras partes del programa y qué funciones son solo funciones auxiliares que se supone que se deben usar localmente.

La mayoría de los lenguajes de programación contienen todo tipo de trucos y mecanismos para abordar la segunda preocupación: en C puede hacer que las funciones sean locales al archivo .c y no incluirlas en el encabezado, en Java o C ++ puede hacer que los métodos sean privados para la clase para garantizar no están expuestos a nivel mundial, y así sucesivamente.

Por lo tanto, el primero de estos problemas suele ser más preocupante, especialmente con los programadores principiantes. Como tal, su profesor de ciencias de la computación le da consejos válidos, pero es importante aplicar estas reglas entendiendo para qué sirven, en lugar de dogmáticamente. Ciertamente, hay casos en los que es mejor, o al menos no perjudicial, hacer varias cosas en una sola función. Es como aprender cuándo debes escribir “quién” en lugar de “quién”: la mayoría de las veces es un buen consejo, pero a veces, incluso cuando “quién” es “correcto”, te hace sonar como un idiota.

Mi regla general es que si tengo un bloque de código con una semántica clara (es fácil describir lo que se supone que debe hacer) y una interfaz pequeña (un número limitado de valores que entran y un número limitado de valores que vuelven a aparecer, y pocos o ningún efecto secundario), luego lo convierto en su propia función. Además, si una función es más larga que, digamos, 25 líneas, eso es una indicación de que podría necesitar hacer una reestructuración.

Una función debe realizar una sola tarea que tenga algún valor. Puede tomar una o más líneas para lograrlo (incluida la llamada a varias otras funciones). Hay varias razones para eso:

Modularidad y legibilidad

Imagina que tienes una receta de pastel (función de horneado) que es solo una larga lista de instrucciones, desde romper huevos y batir la mantequilla hasta poner chispas encima de un producto terminado. Es mucho mejor agruparlos en tareas de alto nivel para que su función se lea como una historia:

función bakeCake () {

  • makeFrosting ();
  • makeBatter ()
  • hornear();
  • montar();
  • Decorar();

}

Reutilizar

Imagina que quisieras usar el mismo glaseado que una salsa de fruta. Como lo pusimos en una función makeFrosting, puede hacerlo, pero si las instrucciones de glaseado simplemente se enumeran directamente en la función bakeCake, no podría decirle a su robot de cocción que haga el glaseado sin hacer todo el pastel

¿Cuántas líneas debe tener una función?

No hay nada de malo en una función que tiene 5 o 10 líneas, sin embargo, cuanto más larga sea, más difícil será leerla. Es mejor evitar funciones de más de 20 líneas. Aquí está la regla general: no debería tener que desplazarse para ver la función completa. Sin embargo, la mayoría de mis funciones son de 1 a 10 líneas, con un promedio de alrededor de 5. Eso es simplemente porque es difícil escribir más de 5 líneas de código sin hacer más de una tarea útil, reutilizable y bien definida. Si puede identificar dos o más cosas lógicas en un bloque de código, defina una función para cada una y llámelas

One Liners

Imagine que tiene una regla de que su compañía de tarjeta de crédito puede renunciar a un cargo por pago atrasado si el cliente no tuvo pagos atrasados ​​en el último año y gasta en promedio más de $ 5,000 al mes. Tu puedes decir:

if (numberOfLaterPayments == 0 y averageTransactionTotal> 5000) {

  • refundFee ();

}

Eso funciona, pero es aún mejor crear una función:

isEligibleForFeeWaiver () {

  • return numberOfLaterPayments == 0 y averageTransactionTotal;

}

Ahora el código es mucho más legible y la regla se puede reutilizar:

if (isEligibleForFeeWaiver ()) {

  • refundFee ();

}

One liners puede ser una herramienta valiosa para escribir códigos legibles e intencionales. Cuando se combina con un buen nombre, la intención es tan obvia que ni siquiera necesita comentarios. Aquí hay otra regla general: si tiene que escribir un comentario para explicar qué está haciendo un bloque de código, tal vez no sea lo suficientemente legible. Divídalo en funciones bien nombradas

Complejidad Ciclomática

Esta es una medida de la complejidad de su función. En términos simples, cuantos más bucles, condiciones, etc. tenga su función, menos legible es y el potencial de errores es mucho mayor. Es bueno mantener baja la complejidad para evitar códigos complejos que son difíciles de razonar. Evite demasiadas condiciones anidadas, bucles, etc. En lugar de tener un bucle dentro de un bucle, haga que el bucle interno sea una función separada, etc.

Refactor

Está bien escribir una función más larga, luego retroceder y ver si encuentra patrones, repeticiones o varias cosas. Si es así, divídalo en funciones más pequeñas y repita hasta que esté completamente satisfecho

¿Hay alguna posibilidad de que hayas malinterpretado a tu maestro?

Decir que una función debe contener una sola línea es realmente ridículo. Sin embargo, la pauta general que la mayoría de los buenos programadores intentan seguir es que una función debe hacer una cosa. Ahora, el concepto de “una cosa” no está muy bien definido, y para aplicarlo correctamente debe comprender por qué esa directriz es importante.

Razón 1: legibilidad

La razón principal, al menos en lo que a mí respecta, es que permite a las personas comprender más o menos lo que hace la función sin tener que leerla. Imagina este código (Java, pero espero que alguien pueda analizar esto):

void main (String [] args) {
Datos de datos = nuevos datos ();

processArguments (argumentos);
if (dumpRequested) {
dumpData (datos);
} más {
fetchData (datos);
}
int respuesta = computeAnswer (datos);
System.out.println (“La respuesta es” + respuesta);
}

Algo aquí es extraño: ¿por qué fetchData solo se llama si dumpRequested es falso? ¿No deberíamos buscar siempre los datos? Bueno, resulta (en nuestro código hipotético) que dumpData también dumpData los datos antes de descargarlos.

¿Esperabas eso cuando leías el código? Por supuesto no. Este es el tipo de sorpresa desagradable que termina causando un dolor enorme y volviendo loca a las personas como yo.

¿Como paso? Bueno, tal vez comenzó lo suficientemente inocente. Por ejemplo, el código podría haberse visto así inicialmente:

void main (String [] args) {
Datos de datos = nuevos datos ();

Opciones opciones = processArguments (args);
if (options.dumpRequested) {
dumpData (datos);
} más {
int respuesta = computeAnswer (datos);
System.out.println (“La respuesta es” + respuesta);
}
}

Quizás en ese momento los datos se leyeron de algún archivo local, o incluso se inicializaron en la memoria o algo así, por lo que no había necesidad de buscarlos; y solo volcamos los datos o calculamos la respuesta, pero nunca ambos. Aquí, la función dumpData solo hace “una cosa”: realiza la solicitud del usuario para volcar los datos.

Entonces alguien tuvo la idea de que es mejor obtener los datos del servidor remoto, porque SOA es donde está hoy en día. Entonces hicimos un cambio:

void main (String [] args) {
Datos de datos = nuevos datos ();

fetchData (datos);
Opciones opciones = processArguments (args);
if (options.dumpRequested) {
dumpData (datos);
} más {
int respuesta = computeAnswer (datos);
System.out.println (“La respuesta es” + respuesta);
}
}

Pero luego nos dimos cuenta de que hay un montón de programas que llaman dumpData , y es estúpido que todos tengan que llamar a fetchData primero, ¿verdad? Así que trasladamos ese fetchData a dumpData y agregamos una llamada separada antes de processData . En este punto, el propósito de dumpData no ha cambiado realmente, excepto que tiene que hacer un poco más para hacerlo.

Pero luego decidimos que eliminar los datos debería ser solo una acción adicional, además de calcular la respuesta. Entonces el código se convirtió en:

void main (String [] args) {
Datos de datos = nuevos datos ();

Opciones opciones = processArguments (args);
if (options.dumpRequested) {
dumpData (datos);
}
fetchData (datos);
int respuesta = computeAnswer (datos);
System.out.println (“La respuesta es” + respuesta);
}

Esto se está volviendo peligroso ahora, porque la naturaleza de la “única cosa” que dumpData hace ha cambiado un poco. Esto debería haber llevado a un replanteamiento, pero no lo hizo. Y finalmente, el golpe mortal: el código anterior es claramente ineficiente, ya que recupera los datos dos veces, por lo que solo hicimos la solución conveniente y “simple” de llamar a fetchData solo cuando dumpData no ha sido llamado.

Ahora, ¿cuál es el propósito de dumpData ? Ya no es solo volcar los datos. En este código, también sirve para obtener los datos en el caso donde se solicitó el volcado. Ya no es una función de un solo propósito.

Puede mejorar un poco las cosas simplemente cambiándole el nombre a fetchAndDumpData , lo que hace que el código anterior sea mucho más legible. Pero eso no ayuda con las otras razones de la regla de “una cosa”, que discutiré a continuación.

Razón 2: contratos

Su función, como cualquier otra unidad de código (clase, módulo, aplicación) debe tener un contrato bien definido y exigible. El contrato consiste en lo que su función espera de sus llamantes y lo que les promete a cambio.

Por ejemplo, la firma de un método Java es parte de su contrato: si me llamas con un int y una String , devolveré un boolean . El compilador lo aplica: si un cliente intenta pasar los parámetros incorrectos, no compilará; Si el método intenta devolver el tipo incorrecto, no se compilará.

La semántica del método, incluidos los argumentos y el valor de retorno, debe formar parte del contrato, que es transmitido por el Javadoc. Esta parte de su contrato se aplica mediante pruebas unitarias.

Cuando agrega funcionalidad a un método, tiene dos opciones: o lo hace parte del contrato o no (duh). Si lo hace, significa que a partir de ahora debe mantener esa funcionalidad, porque otros confiarán en ella. También necesita encontrar una manera de hacer cumplir eso, por ejemplo, escribir buenas pruebas unitarias para ello.

Si decide que esto es demasiado complicado, o que no quiere estar obligado para siempre por este contrato, entonces realmente desea que sus clientes no confíen en él, lo que idealmente significa que al menos deberían fingir que no saben al respecto Sin embargo, eso podría hacer que hagan algo que tenga consecuencias negativas, como obtener los datos dos veces en nuestro ejemplo. Si evitan eso, confiando en su nueva funcionalidad, ahora tiene una dependencia no contractual, que no se aplica. Si cambia su código mañana, los romperá, y no hay protección contra eso. Esto sucede sorprendentemente a menudo en bases de código grandes, diversas y de larga vida, es decir, más del 90% del código de la industria.

Al adherirse a la directriz de “una cosa”, hace que sea más fácil mantener su contrato bien definido y exigible. Si necesita agregar más funcionalidad, divídala en otros métodos / clases / etc., y defina sus contratos como “una cosa”.

Razón 3: Acoplamiento

Finalmente, llegamos a uno de mis favoritos personales como desarrollador: el acoplamiento. Además de ser un programa de la BBC bastante divertido, el acoplamiento es una medida de cuánta interdependencia hay entre diferentes unidades de código. En general, desea que el acoplamiento sea mínimo, porque cuanto más alto (o más apretado ) sea, más difícil será cambiar una de las unidades sin tener que cambiar, y reconstruir, volver a probar y volver a implementar las otras. Dado que el cambio es la única constante en la ingeniería de software del mundo real, esto es extremadamente importante.

Regrese a nuestro ejemplo. Digamos que el método fetchData ahora requiere un parámetro, por ejemplo, la URL de la que se obtienen los datos, y que esto se pasa como un argumento de línea de comando. ¿Como hacemos que esto funcione? Probablemente necesitemos hacer algo como esto:

void main (String [] args) {
Datos de datos = nuevos datos ();

Opciones opciones = processArguments (args);
if (options.dumpRequested) {
fetchAndDumpData (options.dataUrl, data);
}
fetchData (options.dataUrl, data);
int respuesta = computeAnswer (datos);
System.out.println (“La respuesta es” + respuesta);
}

Tenga en cuenta que tenemos que pasar la URL para fetchAndDumpData , a pesar de que es completamente innecesario para el “objetivo principal” de ese método, que es volcar los datos. Recuerde que la recuperación fue realmente solo una optimización. Cuantas más “cosas” diferentes haga su función, más va a suceder.

Si la función solo hace “una cosa”, entonces, por definición, solo necesita saber las cosas que se requieren para hacer esa única cosa. Puedes cambiar el resto del mundo a su alrededor y no le importaría. Eso es acoplamiento flojo, y eso es algo bueno.

Entonces, ¿qué haría aquí? Hay muchas maneras diferentes de volver a “una cosa” y reducir el acoplamiento. Por ejemplo, puede decir que el trabajo de la persona que llama es buscar los datos y hacer que esa parte del contrato para dumpData ; o puede proporcionar dumpData con una dumpData bien definida de “proveedor de datos” (que tal vez enfrente un caché en la implementación final); o puede refactorizar dumpData en la clase Data ; Todos estos (y estoy seguro de que hay otros) son razonables, y elegiría uno en función de los detalles.

Conclusión:

Siempre piense en lo que su función está tratando de lograr; ¿Cuál es su propósito ? Como mencioné anteriormente, esto se aplica a casi cualquier unidad de código: una función / método, una clase, un módulo, un servicio, una aplicación, una página web, incluso una sola línea de código o un bucle. Cuanto más enfocado y específico sea ese propósito, más fácil será comprender , mantener y cambiar , lo cual es el sello distintivo de una buena ingeniería de software.

Lo importante es que el propósito de la función es que sea comprensible para un lector humano. No es que se compila y aparentemente produce un resultado correcto.

Preferiblemente, lo que hace la función debe estar claro a partir del nombre de la función. De esta manera, no tendrá que mirar demasiado el código fuente o la documentación de la función para comprender el código donde se usa. Si necesita buscar la fuente, no ha podido abstraer el propósito de la función y no ha obtenido ninguna legibilidad del código, sino que lo ha ofuscado al obligar al lector a mirar dos lugares en lugar de uno.

Como ayuda para aclarar el propósito de una función, es bueno seguir el “Principio de responsabilidad única”, es decir, la función debe hacer una cosa. Esto no significa que deba tener una línea.

Otra ayuda es observar la complejidad, es decir, las ramas como en las declaraciones condicionales y los bucles. Si tiene diez rutas diferentes a través de una función, un lector tendrá problemas para comprender todos los aspectos de la función. Y tendrá que escribir muchas pruebas unitarias para probar completamente la función.

Además, el lenguaje de programación es importante. Las funciones C de 20 o 50 líneas no son infrecuentes o incluso una mala práctica, suponiendo que estén bien escritas. Si terminas con un trazador de líneas de 20 en Haskell, probablemente deberías pensar de nuevo.

Con respecto a las funciones de una línea: en mi opinión, están bien si cumplen un propósito, pero no es un objetivo producir un programa que consista en funciones de una línea. Si el código es más legible como una entidad de 10 líneas que 3 funciones, cada una de las 4 líneas que se llaman entre sí, prefiero la función única. Muchos “gurús” afirman que las funciones deberían ser “lo más simples posible”, pero tienden a sub-optimizar la competencia local a expensas de la complejidad global.

Examinemos la primera pregunta, que es: ¿Por qué está creando una función en lugar de hacer que su programa principal haga todo el trabajo? Respuesta: Está creando una función que se puede llamar desde diferentes lugares en su programa principal. Entonces, ¿por qué no crear esa función All-In-One que propusiste? Porque es menos probable que necesites esa función todo en uno. Si crea funciones separadas de un solo propósito como sugiere su profesor de CS, es más probable que necesite algunas de esas funciones incluso cuando no las necesite todas juntas.

Una vez hice un proyecto de programación autodirigido utilizando un lenguaje de programación inicial que tenía una función de suma pero no otras funciones aritméticas. Para la resta, hice que el segundo número fuera negativo y agregué el primer y (negado) segundo número (6 + -2) para obtener 4. Luego programé una función de multiplicación que añadía repetidamente. Luego programé una función de división que realizaba división larga; llamó a la función de multiplicación y a la función de resta para hacer su trabajo. Luego creé un generador de números primos que proponía cada número impar como posible primo; trató de dividir por cada Número primo ya conocido hasta que encontró uno que se dividió en partes iguales, momento en el cual pasó al siguiente número impar; Si no pudo encontrar un divisor, agregó el número impar a su lista cada vez mayor de números primos. Dos de las limitaciones del lenguaje era que una función solo podía devolver un único resultado, y que el lenguaje solo podía manejar enteros (sin decimales ni fracciones). Pronto descubrí la necesidad de un clon de la función División que devolvería el Resto en lugar del Cociente; Esto fue útil en el Generador de números primos, ya que cuando hice las divisiones de mi número objetivo por cada uno de mis números primos conocidos, solo me importaba si tenía un resto cero (lo que significa que estaba dividido en partes iguales) o un resto distinto de cero (lo que significaba que no se dividió equitativamente).

Por cierto, antes de que cada número primo se probara como un posible factor, el generador de números primos elevaría al cuadrado el número candidato para ver si el resultado era mayor que el número objetivo; si era más grande, entonces el trabajo ya estaba hecho. Por ejemplo, al probar si 29 es Prime, verificaría 3 (el resto es 2, por lo que no es un factor), luego verificará 5 (resto 4). Una vez que se dio cuenta de que 7 al cuadrado era mayor que 29, sabía que 29 debía ser un número primo porque si 7 era uno de los factores, entonces el otro factor tendría que ser menor que 7 y la función ya había descartado todos los números primos más pequeños como posibles factores

Mi punto es que tener cada una de estas funciones separadas disponibles facilita cada tarea nueva; solo era cuestión de elegir las herramientas adecuadas para cada nueva tarea.

El consejo de “hacer solo una cosa por función” no significa que solo debe contener una línea.

Como ejemplo, suponga que tiene que escribir un programa que lea en una lista de números e imprima los mismos números, pero en orden creciente. En el nivel superior, este programa tiene que hacer tres cosas: leer en la lista, ordenarlos en orden creciente y luego generar la lista. En lugar de agrupar todo eso en un gran “principal”, su maestro le está diciendo que ponga cada una de esas cosas en una función separada.

A su vez, cuando va a escribir la función que ordena la lista, supongamos que decide usar el algoritmo QuickSort. Este algoritmo primero elige un elemento “pivote”, divide la lista en dos partes, una con todo lo que debe venir “antes” del pivote y otra con todo después, luego (recursivamente) clasifica esas listas y las vuelve a unir. Son cuatro cosas: elegir el pivote, dividir la lista, ordenar las listas y volver a unirlas.

En un lenguaje de procedimiento típico, la opción “elegir un pivote” suele ser trivial: puede usar cualquier elemento que aparezca en la lista. Dividir las listas puede ser algo complicado, pero la ordenación recursiva y volver a unirlas suelen ser simples. Esto sugiere que la función “ordenar” debería tener un ayudante que haga la “única cosa” de dividir la lista.

Si imagina el programa final, quizás sus funciones se denominen main , readList , sort , splitList y writeList . Ninguno de ellos tiene una sola línea de código, pero cada uno hace una sola cosa. Esto hace que sea mucho más fácil razonar sobre ellos: si tienen que hacer más de una cosa, es más difícil demostrar que realmente los hacen todos.

Al usar esta estrategia, generalmente, terminas con programas con los que es mucho más fácil trabajar. Suponga que encuentra que el programa está dando un resultado incorrecto, tal vez no imprime todos los números originales y siempre omite el primero. La asignación cuidadosa de responsabilidades, una por función, le ayuda a saber dónde comenzar a buscar el problema. Aquí, querrá ver la función writeList : obtiene una lista de números y los imprime, así que omite el primero o de alguna manera no recibe el primero. Si es lo último, debe observar cómo la función de clasificación se une a las listas.

Cuando sus programas comienzan a crecer, no puede visualizar todos los detalles al mismo tiempo, por lo que naturalmente divide las cosas jerárquicamente: esta jerarquía está directamente relacionada con la forma en que divide el programa en subunidades (funciones, módulos, clases, o lo que sea), y tienen una relación uno a uno, o casi, entre las “unidades” mentales (es decir, las cosas que hace el programa) y las “unidades” del programa (que implementan la funcionalidad), es mucho más fácil pensar en todo el asunto.

Sólo una cosa.

Si está haciendo dos cosas en una función, divídala en dos funciones.

De esta manera, puede reutilizar la mayoría de las funciones que creó y su proyecto se escalará mejor.

Por ejemplo: no realice una función para leer la contraseña del usuario, autenticar y redirigir a su página de perfil. Cree 3 funciones, una para cada tarea, y luego cree una función para llamar a las 3 funciones que acaba de escribir. Más tarde, le permitirá autenticar al usuario de otra manera, o redirigir a la página de perfil desde otra página, sin tener que volver a escribir el código.

El número de líneas no importa, el objetivo real que debe tener en mente es: una función = solo una acción

Lo que su profesor de informática le está diciendo es la forma actual de pensar. (No diré que es correcto porque las personas tienen opiniones muy diferentes. También existe el argumento de que cuando el código se vuelve muy abstracto, también es muy malo)

Mucha gente siente que una cosa por función es la mejor manera de hacerlo, ya que también puede reutilizar fácilmente su código en otro proyecto. También hace que la función sea muy limpia y simple. Si una función está haciendo demasiadas tareas, es realmente difícil para alguien leer y depurar.

Invierta su pregunta: ¿por qué es una mala idea implementar una aplicación completa en una sola función? Se me ocurren dos buenas razones.

Primero, a menudo habrá bits de lógica de programa que se usan en muchas áreas diferentes de la aplicación. Si cada parte discreta de la lógica del programa se implementa en una única función que se invoca cada vez que se necesita esa lógica, entonces solo hay una parte del código que debe depurarse inicialmente y modificarse posteriormente si la lógica cambia. Este uso de funciones hace que el proceso de desarrollo sea más fácil y rápido.

En segundo lugar, antes de que pueda depurar o modificar la aplicación, debe encontrar el código que necesita funcionar. Esto puede llevar mucho tiempo si una función es grande. Por otro lado, si una función potencialmente grande se divide en partes lógicas de trabajo, cada una de las cuales se implementa en una subfunción con un nombre apropiado y se invoca desde la función principal, puede ser más fácil navegar a través de la base de código y encuentra el bit que necesita ser modificado.

Insistir de forma pendiente en que cada función tiene una sola línea está mal. Por otro lado, tener una sola función para una aplicación completa también está mal. Desea encontrar un punto medio entre estos dos extremos que maximice la productividad tanto de los desarrolladores iniciales como de las personas que tienen que mantener la aplicación durante toda su vida.

Una función o método (en lenguaje orientado a objetos) debe hacer una cosa y solo una cosa. Cuando digo una cosa Permítanme ilustrarlo con un ejemplo. Supongamos que está escribiendo una aplicación para calcular la suma, diferencia, producto y división,

Debe tener un método por operación, no debe mezclar suma y diferencia en el mismo método. Esto mantiene su código limpio y modular y fácil de extender y depurar

En resumen, esto se llama patrón de responsabilidad única.

En términos de práctica aceptada, restringir las funciones para que sean una sola línea no hace el corte. De hecho, lo llamaría tonto. (Descargo de responsabilidad: su maestro puede estar tratando de hacer un punto, lo que no sería tonto en absoluto). Sin embargo, se acepta la práctica de que una función debe realizar una operación (conceptual). Puede ser un solo paso o puede implicar varios pasos. La clave es que la respuesta a la pregunta “¿qué hace esta función?” Debe ser una oración simple y simple (por ejemplo, “escribe los datos de la transacción actual en la base de datos” o “calcula todas las raíces del polinomio pasado como argumento “o” devuelve el cuadrado de su argumento “). Si encuentra que necesita usar “y” en la descripción (por ejemplo, “lee los datos del usuario y calcula el promedio”), entonces es una pista fuerte de que tal vez debería desglosarse en funciones conceptualmente más simples.

Otra forma de pensarlo es que debe haber exactamente una razón para escribir la función en primer lugar. Debe cumplir exactamente una necesidad en el programa general.

Tenga en cuenta que esto no está relacionado con “cuánto trabajo” debe hacer una función. Después de todo, si está programando en, digamos, C, su función main() hace todo el trabajo que su programa va a hacer.

No estoy seguro de dónde entra en juego la teoría de la “línea única”. Si puede hacer lo que necesita hacer con una sola línea, ¿por qué crear una función?

Una función debe tener una salida, generalmente. En ese sentido, sea lo que sea que esté llamando a la función, debe hacerse con la intención de esa única salida.

Podría crear una función que agregue un punto a cada línea de texto en una lista, por ejemplo, y esa sigue siendo solo una salida.

Podría crear una función que analice una dirección en tres campos desde uno y que sería una salida (string ()).

No debería, correctamente, usar una función para leer datos de un archivo de texto a una lista (de cadena), identificar los campos de dirección de esa cadena y generar tanto la lista (de cadena) como la lista (de direcciones) usando una variable global para la lista de direcciones y actualizarla dentro de la función. En cambio, recomendaría una nueva función para eso.

  1. Legibilidad (por ti mismo y por otros).
  2. Reutilización de fragmentos de lógica en otras compilaciones.
  3. Más especificidad en el lanzamiento de errores, especialmente si maneja errores con información de origen.
  4. En los viejos tiempos (tal vez ahora incluso), más uso del espacio de pila que el montón, que puede ser más rápido, si puede evitar mallocs y variables inicializadas.

Sí, para cumplir con la filosofía de Unix, una función debe cumplir un único propósito. Sin embargo, decir que una función solo debe contener 1 línea es un poco extremo. Una función debe hacer lo que dice en la lata. No agregue más funciones a una función que no la necesita. Como regla general, una función al menos debería caber completamente en su pantalla. Si no es así, puede tener una idea equivocada sobre lo que debe hacer esta función.

Su profesor de informática está en lo correcto cuando dice que solo debe hacer una cosa por función.

Algunas funciones solo contendrán una sola línea y eso puede parecer una pérdida de tiempo ahora, pero asegurarse de que una función solo haga una cosa ayudará más adelante cuando intente llamar a estas funciones y tratar de descubrir qué hacen.

En el momento en que tenga que hacer algo por segunda vez, extráigalo en una nueva función. Esto es lo suficientemente bueno, ya que el uso actual le dirá dónde deben dividirse sus funciones. No haga divisiones ciegas en el primer uso, esto lo dejará con una distracción incorrecta que dificultará las divisiones posteriores.

Estoy de acuerdo con tu profesor comp.sci.

Muy a menudo termino con una tonelada de funciones de una, dos o tres líneas, luego unos 10 y 20 revestimientos … cuando se hacen mucho más grandes que eso, empiezo a querer dividir algunos.

Una buena regla general es preguntarse con qué frecuencia necesitará repetir ese código. Si necesita repetirlo con frecuencia, pertenece a una función para que pueda condensar su código y hacerlo más legible y más fácil de depurar.

Si es un código único, no debería tener su propia función, eso es un desperdicio.

una buena práctica es que debe ser lo suficientemente pequeño como para entenderlo fácilmente

la práctica razonable es una página, por lo que puede verlo todo de una vez

la práctica en el mundo real es “mientras sea necesario” … el récord actual de cualquier cosa en mi experiencia personal es de poco más de 10,000 líneas. solo no preguntes

“Hacer una cosa” probablemente no sea una mala manera de decirlo, pero no en el sentido de “realizar una línea de código” sino más bien “hacer una cosa sucintamente, y no confundir las cosas haciendo otra también”