Diseñar funciones que puedas probar

Ciencia y tecnologíaDiseñar funciones que puedas probar

Una función útil no se define por tener pocas líneas. Se define por cumplir una responsabilidad que puedes explicar y comprobar. En el curso anterior aprendiste a crear funciones. Ahora vas a mejorar su diseño para que el inventario pueda crecer sin depender de variables globales ni de preguntas por teclado escondidas entre los cálculos.

Escribir el contrato antes del cuerpo

Un contrato sencillo responde a tres preguntas: qué recibe la función, qué devuelve y qué errores comunica. Por ejemplo, ajustar_cantidad recibe un artículo y una variación entera. Devuelve un artículo nuevo con la cantidad actualizada. Si el resultado sería negativo lanza ValueError. No pregunta nada por teclado y no guarda archivos.

Este contrato permite utilizar la operación desde una terminal, una prueba o una futura interfaz. La parte que llama a la función decide cómo recoger los datos y cómo mostrar el resultado. La función se concentra en una regla: no permitir existencias negativas. Esa separación reduce las situaciones que necesitas preparar para comprobarla.

def ajustar_cantidad(articulo, variacion):
    nueva = articulo["cantidad"] + variacion
    if nueva < 0:
        raise ValueError("No hay unidades suficientes")

original = {"codigo": "CU001", "cantidad": 3} ajustado = ajustar_cantidad(original, -2) print(original["cantidad"]) print(ajustado["cantidad"]) «`

Las salidas son 3 y 1. La construcción con dos asteriscos copia las parejas del diccionario y la clave posterior sustituye la cantidad en el nuevo resultado. Para nuestros campos simples es suficiente. Si el diccionario contuviera listas interiores esas listas seguirían compartidas: no estamos realizando una copia profunda de cualquier objeto posible.

Devolver un resultado y mostrarlo son acciones distintas

print escribe para una persona. return entrega un valor al código que llamó a la función. Si una función solo imprime el total no podrás sumarlo fácilmente a otro cálculo. Tendrías que rehacer la operación o intentar recuperar texto de la pantalla. Devuelve los datos y deja que otra parte decida su presentación.

También puedes devolver None cuando una operación no tiene un resultado útil pero debe quedar claro en el contrato. No combines sin explicación números válidos, cadenas de error y None. Quien utilice la función tendría que adivinar qué significa cada caso. Para entradas inválidas utilizaremos excepciones concretas y para resultados correctos un tipo previsible.

Evitar dependencias ocultas

Una variable global parece cómoda porque puedes consultarla desde cualquier función. El problema aparece cuando su valor cambia en un lugar distante. Una prueba que antes funcionaba puede fallar dependiendo del orden de ejecución. Pasa los datos necesarios como parámetros para que cada llamada muestre de qué depende.

No significa que todas las constantes deban convertirse en argumentos. Una cadena fija con caracteres permitidos puede vivir a nivel de módulo. La diferencia está en si representa una regla estable o un estado que cambia durante el programa. Un contador global de artículos sería estado compartido. El límite de longitud de un código expresa una decisión del formato.

El peligro de los valores mutables por defecto

No definas una función con una lista vacía como valor por defecto si vas a modificarla. Ese objeto se crea al definir la función y puede reutilizarse en llamadas posteriores. Dos ejecuciones que creías independientes terminarían compartiendo datos. Utiliza None como valor por defecto y crea la lista dentro cuando sea necesario.

def agregar_nota(texto, notas=None):
    if notas is None:
        notas = []

print(agregar_nota("Revisar cuadernos")) print(agregar_nota("Contar lápices")) «`

Cada llamada sin lista produce un resultado independiente. Además, la suma crea una nueva lista en lugar de añadir elementos a la que recibes. Debes decidir si quieres modificar una colección o devolver otra. Lo importante es que el comportamiento sea intencionado y esté explicado. No todas las funciones tienen que ser puras pero los efectos deben resultar visibles.

Dividir cuando hay responsabilidades diferentes

Una función que valida una cantidad, abre una base de datos, inserta un artículo y pregunta si quieres continuar mezcla varias tareas. Separaremos la conversión de entradas, el almacenamiento y la interfaz. Si falla la conversión podremos probarla sin crear una base de datos. Si falla el almacenamiento no necesitaremos simular preguntas por teclado.

No fragmentes por cada línea. Si una función auxiliar no expresa una idea propia quizá no aporte claridad. Busca verbos concretos como validar, leer, insertar y exportar. Nombres como procesar_todo o hacer_cosas esconden decisiones. Antes de extraer una función escribe una frase que describa su responsabilidad y comprueba si otra función podría reutilizarla.

Errores habituales

Cambiar una lista recibida sin avisarlo puede alterar resultados posteriores. Otro error es capturar todas las excepciones dentro de la función y devolver cero: entonces un fallo parece una cantidad válida. Conserva el error hasta una capa que pueda explicar qué hacer. En el capítulo de excepciones aprenderás a situar esa frontera.

También es fácil escribir funciones que dependen de que otra se haya ejecutado antes para preparar una variable global. Si existe una dependencia pásala como argumento o reúna la inicialización en un lugar explícito. Una función debería poder leerse sin investigar toda la historia del programa para saber qué necesita.

Lo que deberías recordar

  • Una función debe tener entradas y resultados claros.
  • return y print resuelven tareas diferentes.
  • Los cambios sobre entradas deben ser intencionados.
  • Evita listas mutables como valores por defecto.

Ejercicio práctico

Ejecuta ajustar_cantidad con variaciones de menos dos, cero y cinco. Anota tanto el resultado devuelto como el contenido del artículo original. Después prueba una variación de menos cuatro y comprueba que se comunica un error antes de crear un resultado inválido.

Escribe el contrato de una futura función que sume las unidades de varios artículos. Debe recibir una colección y devolver un entero sin imprimir. Implementa la función con el bucle que ya conoces y úsala dos veces con colecciones diferentes. El resultado no debe depender del orden de las llamadas.

Repite agregar_nota sin proporcionar una lista y después pasando una lista existente. Comprueba que la lista original permanece igual. En el cuaderno explica cuándo preferirías ese comportamiento y cuándo aceptarías modificar la entrada. Lo que debes entregar es una decisión razonada junto a una ejecución que demuestre que tu código la cumple.

Siguiente: Separar el código en módulos claros.

Volver al índice de Programar con Python.

Últimos posts

DEJA UNA RESPUESTA

Por favor ingrese su comentario!
Por favor ingrese su nombre aquí

 

Artículos más vistos

Horóscopo diario
Menú diario