🥇 Funciones

En los primeros días de la programación se crearon sistemas con rutinas y subrutinas. En la era de Fortran se utilizaron programas, subprogramas y funciones. De esa época solo sobreviven las funciones. Una función es la primera linea de organización de un programa.

🍿 ¿Cuáles son las buenas prácticas al momento de crear funciones?

🥤 Crea funciones pequeñas

La primera regla de las funciones es que deben ser pequeñas. Durante los 80’s se solía decir que las funciones no deberían ser mas grandes que el alto de la pantalla. En ese momento esta resolución era equivalente a 24 lineas por 80 columnas y de estas 4 líneas eran utilizadas para propósitos administrativos.

🥤 Utiliza indentado en las funciones

Los bloques dentro de sentencias como if, else, while, etc. deben de contener una sola línea. Esto implica que la mayoría de las ocasiones esto implique que solo realicemos una llamada a una función, lo que no solo mantiene la función pequeña, también agrega un valor de documentación ya que la función llamada dentro del bloque puede tener un nombre bastante descriptivo.

Las funciones no deben ser tan largas que contengan estructuras anidadas, por ello el indentado no debe superar uno o dos indentaciones. Esto hace que la función sea fácil de leer y entender.

🥤 Asegúrate de que cada función realiza una sola tarea

Las funciones deben de realizar una sola tarea y deben de hacerla de forma correcta. El reto se encuentra en determinar que tipo de tarea es la que tienen que realizar, para ello es recomendable que se pueda describir la función en forma de párrafo.

La razón por la que escribimos funciones es porque deseamos descomponer un concepto largo en un número de pasos en el siguiente nivel de abstracción.

Otra forma de saber que una función esta haciendo mas de una cosa, es determinar si se puede extraer otra función a partir de la primera. Las funciones que solo hacen una cosa, no pueden ser descompuestas en mas funciones.

🥤 Mantén un solo nivel de abstracción en cada función

Para asegurarte que las funciones solo hacen una cosa, cerciórate de que las sentencias dentro de cada función se encuentran al mismo nivel de abstracción.

Mezclar niveles de abstracción dentro de una función es siempre confuso. Los lectores pueden no tener la posibilidad de determinar si una expresión es esencial o solo un detalle.

🥤 Lee código en forma descendente (de arriba hacia abajo)

Asegúrate que el código pueda ser leído de arriba hacia abajo. De forma que una función sea seguida por otra que se encuentre en el nivel siguiente de abstracción.

En otras palabras deseamos que el código pueda ser leído como si fuese una serie de párrafos, cada uno de estos define un nivel de abstracción.

Se vuelve complicado el poder seguir esta regla debido a que hay que escribir funciones que se encuentren solo en un nivel de abstracción pero poder aprender esto resulta muy importante.

🥤 Evite bloques switch repetitivos

Es difícil escribir sentencias switch pequeñas como lo es que este tipo de sentencias solo realice una actividad ya que las sentencias switch siempre realizan varias actividades. No siempre se puede prevenir el uso de sentencias switch pero podemos asegurarnos de que cada sentencia switch se use en una clase de nivel inferior y nunca se repita. La regla general sería entonces que se utilice una sola sentencia switch y esta sea solo para crear objetos polimórficos.

🥤 Utilice nombres descriptivos

Mientras mas pequeña una función es, mas sencillo es elegir un nombre descriptivo, sin embargo se recomienda no tener miedo a utilizar nombres largos. Un nombre largo y descriptivo es mejor que uno corto que deje la intención de este a la imaginación.

No hay que tener miedo de invertir tiempo en elegir un nombre. Trata de utilizar diferentes nombres y lee el código para cerciorarte de que es acorde a este cada vez que elijas uno nuevo.

Elegir nombres descriptivos te ayudará a clarificar el diseño del módulo y al mismo tiempo a mejorar este.

Ser consistente en los nombres utilizando las mismas frases, sujetos y verbos en el nombre de las funciones que se usan.

🥤 Use la menor cantidad de argumentos posible

El número ideal de argumentos a una función es cero. Mas de tres argumentos deben tener una muy buena justificación debido a que los argumentos toman demasiado poder conceptual.

Desde el punto de vista de las pruebas, los argumentos son mas difíciles de probar ya que se tienen que poder probar todas las combinaciones posibles de ellos. Si solo existe un argumento en la función esto no resulta un gran problema, con mas de dos argumentos, escribir las pruebas necesarias se vuelve un problema.

Los argumentos de salida son incluso mas complicados de entender que los argumentos de entrada. Usualmente no se desea que la información pase a través de argumentos de entrada, por lo cual se vuelve mas complicado aun lidiar con argumentos de salida.

🥤

Existen dos razones para enviar un solo argumento a una función. Un ejemplo es cuando se solicita un valor de entrada a través de una pregunta, otra puede ser la validación de una entrada como existeElArchivo(archivo) o esValidaLaFecha(fecha). O puede ser que se este transformando un valor en otro como leerArchivo(archivo) o convertirHorasEnSegundos(horas).

Para funciones con un argumento considere que existe un evento cuando no exista un argumento de entrada pero no existan argumentos de salida. En este caso al invocar el evento se recibirá el valor del argumento para alterar el estado del sistema, por ejemplo:

void contrasenaSeErroNVeces(int intentos)

Utilizar un argumento de salida en lugar de un valor de retorno es confuso. Si una función va a realizar una transformación, dicha transformación debe aparecer como el valor de retorno. Por ello StringBuffer transform(StringBuffer in) es mejor que utilizar void transform(StringBuffer out) aun cuando cuando en el primer ejemplo simplemente retorne el argumento de entrada.

🍿 Evita el uso de argumentos tipo bandera

El uso de los argumentos bandera (flag arguments) es una mala práctica. Enviar un valor boleano a una función complica mas la implementación del método y visibiliza que esta función hace mas de una sola cosa.

🍿 Funciones de dos argumentos

Las funciones que tienen dos argumentos resultan mas complicadas de entender que una que solo tiene uno solo. Sin embargo existen ocasiones en donde hay funciones que por su naturaleza requieren que existan dos o mas argumentos y esto es totalmente válido, por ejemplo.

Cuadrado cuadrado = new Cuadrado(altura, anchura);

La interpretación errónea del sentido de los argumentos sucede incluso en funciones muy sencillas como assertEquals(resultadoEsperado, resultadoObtenido), muchas veces en este tipo de funciones utilizadas en sistemas de pruebas enviamos a la función los argumentos en el orden inverso.

Una forma de mitigar este tipo de problemas es creando una clase que requiera un solo parámetro y después invocando una función que realice dicha comparación recibiendo el otro.

Resultado resultado = new Resultado(valorDelResultado);
resultado.compararConValorEsperado(valorEsperado);

🥤 Funciones de tres argumentos

Las funciones de tres o mas argumentos resultan mas difíciles aun de mantener por lo cual se recomienda que se utilicen con discreción. Las triadas (funciones de tres argumentos) se prestan a bastante confusión si tomamos el ejemplo de las funciones usadas en las pruebas unitarias assertEquals(mensaje, valorEsperado, valorActual) nos damos cuenta que en muchas ocasiones el valor de mensaje que representa la información desplegada en la ejecución de la prueba, es en realidad entendida como cualquiera de los otros 2 argumentos.

🥤 Objetos como argumentos

Si una función requiere mas de dos o tres argumentos, la mejor opción es utilizar un objeto como argumento para este propósito.

// esta funcion
Circulo crearCirculo(double x, double y, double radio);

// se puede simplificar asi
Punto crearPunto(double x, double y);
crearCirculo(Punto punto, double radio);

En el anterior ejemplo se ha abstraído el concepto de punto en su propia clase y después enviada como un parámetro.

🥤 Funciones variádicas

En ocasiones las funciones reciben un numero variable de argumentos, por ejemplo, a estas se les conocen como funciones variádicas. Considera el siguiente ejemplo.

String.format("%s trabajo %.2f horas", nombre, horasTrabajadas)

La función String.format puede recibir un número de argumentos variables, estos dependen de la cantidad de placeholders que el primero contenga. El segundo parámetro es en realidad una lista de parámetros, por ello la función es en realidad una función de solo 2 argumentos y el segundo corresponde a una lista.

Las funciones que reciben argumentos variables pueden ser monads (de un argumento) o dyads (de dos argumentos) o triads (de tres argumentos), pero sería un error si se utilizan mas de tres argumentos para este propósito.

🥤 Verbos y palabras

En el caso de funciones de un solo argumento, la función y el argumento deben tener una forma armónica entre verbo y sustantivo, por ejemplo void escribirEnCampo(nombre), en cuyo caso sabemos que nombre hace referencia al nombre del campo en el cual queremos escribir. Podemos llevar este ejemplo a los ejemplos de las funciones usadas en las pruebas unitarias asegurarseQueEsperadoEsIgualQueActual(esperado, actual)

🥤 No realice efectos colaterales

Un efecto colateral se refiere a realizar algo distinto o adicional a lo que la función promete. A veces sobre el parámetro enviado, en otras ocasiones sobre variables globales. Un ejemplo es cuando creamos una función que valida una contraseña y simultáneamente dentro de la parte positiva de la función realizamos el inicio de sesión del usuario.

🥤 Argumentos de salida

Los argumentos son de forma natural interpretados como entradas a una función. Cualquier cosa que te force a revisar la firma (signature) de una función debe ser evitado. Tomando un ejemplo.

agregarTiempo(minutes);

En el ejemplo anterior no se entiende si se esta agregando minutos al tiempo o tiempo a los minutos. Esto lo podemos solucionar de la siguiente manera.

tiempo.agregar(minutos);

🥤 Separa acciones de las consultas

Las funciones deben hacer algo o preguntar algo, pero no ambas cosas. Ya sea que cambien el estado de un objeto o retornen la información de un objeto, hacer ambas al mismo tiempo genera confusión.

En el siguiente ejemplo…

public boolean set(String atributo, String valor)

Si utilizamos la expresión if(set('usuario','luis')) no podemos distinguir si la intención de la función es asignar el valor del atributo usuario como luis o determinar si el atributo usuario ha sido definido previamente como luis.

Mediante la separación de consultas y acciones podemos primero preguntar si un valor existe y después definirlo.

if(attributeExists("usuario")) {
    setAttribute("usuario","luis");
}

🥤 Da preferencia a las excepciones sobre los error codes (códigos de error)

Retornar códigos de error desde una función que realiza una acción es una violación de la separación de acciones-consultas. Promueve el uso de funciones de acción como expresiones dentro de condicionales como if.

if(borrarPagina(pagina) == E_OK){
}

Debería mantener una separación de la consulta.

try {
    borrarPagina(pagina);
} catch (Exception e) {
    ...
}

🥤 extrae los bloques de captura de excepciones try/catch

Los bloques try / catch no lucen bien, confunden la estructura el procesamiento de errores con el flujo normal del código. Lo ideal es extraer el bloque contenido dentro de un bloque de captura de una función dentro de una variable y el manejo de su error dentro de otra.

public void borrar(Pagina pagina) {
    try {
        borrarPaginaMasReferencias(pagina);
    } catch (Exception e) {
        logError(e);
    }
}

private void borrarPaginaMasReferencias(Pagina pagina) throws Exception {
    borrarPagina(pagina);
    .
    .
    .
}

private void logError(Exception e) {
    logger.log(e.getMessage());
}

Cuando se utilizan error codes se crea una clase o enum que contiene todos los errores definidos.

public enum Error {
    OK,
    INVALIDO,
    NO_AUTORIZADO,
    NO_ENCONTRADO,
    .
    .
    .
}

Esto genera que otras clases deban importar esta clase o numerador lo que genera una dependencia.

Cuando se usan excepciones, las nuevas excepciones derivan de la clase Exception. Pueden ser agregadas nuevas excepciones conforme se vayan necesitando, sin tener que realizar actualizaciones sobre las clases creadas previamente.

🥤 DRY (Don’t Repeat Yourself), No repitas código

La duplicación es un problema debido a que incrementa la cantidad de líneas de código de forma innecesaria, y empuja a mantener varios bloques de código simultáneamente.

La duplicación puede considerarse el origen de la creación de código sucio dentro de un proyecto. Desde la invención de las rutinas en la programación, siempre se ha buscado eliminar la duplicidad en el código.

🥤 Programación Estructurada

Cada función y cada bloque debe tener una entrada y una salida. Esto quiere decir que solo debe haber una sola sentencia return en cada función, sin utilizar break o continue dentro de loops y nunca otro tipo de sentencias como goto. Esto es útil en funciones en donde realmente se aporta bastante beneficio. Mas si se mantienen las funciones pequeñas, y ocasionalmente se utilizan return, break o continue no debe significar un problema.

🍿 ¿Cómo escribir funciones de forma correcta?

🍿 Conclusión

Nomenclatura descriptiva Comentarios dentro del código
comments powered by Disqus