A menudo me encuentro devolviendo un booleano de un método, que se usa en múltiples ubicaciones, para contener toda la lógica alrededor de ese método en un solo lugar. Todo lo que necesita saber el método de llamada (interno) es si la operación fue exitosa o no.
Estoy usando Python pero la pregunta no es necesariamente específica de ese lenguaje. Solo hay dos opciones en las que puedo pensar
- Genere una excepción, aunque las circunstancias no son excepcionales, y recuerde detectar esa excepción en cada lugar donde se llama la función
- Devuelve un booleano como lo estoy haciendo.
Este es un ejemplo realmente simple que demuestra de lo que estoy hablando.
import os
class DoSomething(object):
def remove_file(self, filename):
try:
os.remove(filename)
except OSError:
return False
return True
def process_file(self, filename):
do_something()
if remove_file(filename):
do_something_else()
Aunque es funcional, realmente no me gusta esta forma de hacer algo, "huele" y a veces puede dar lugar a muchos ifs anidados. Pero no puedo pensar en una forma más simple.
Podría recurrir a una filosofía y uso más LBYL os.path.exists(filename)
antes de intentar la eliminación, pero no hay garantías de que el archivo no se haya bloqueado mientras tanto (es poco probable pero posible) y todavía tengo que determinar si la eliminación se realizó correctamente o no.
¿Es este un diseño "aceptable" y, de no ser así, cuál sería una mejor manera de diseñarlo?
Su intuición sobre esto es correcta, hay una mejor manera de hacerlo: mónadas .
¿Qué son las mónadas?
Las mónadas son (parafraseando a Wikipedia) una forma de encadenar operaciones juntas mientras se oculta el mecanismo de encadenamiento; en su caso, el mecanismo de encadenamiento es el
if
s anidado . Esconda eso y su código tendrá un olor mucho más agradable.¡Hay un par de mónadas que harán exactamente eso ("Quizás" y "Cualquiera") y por suerte para ti son parte de una muy buena biblioteca de mónadas de pitón!
Lo que pueden hacer por tu código
Aquí hay un ejemplo usando la mónada "Cualquiera" ("Disponible" en la biblioteca vinculada a), donde una función puede devolver un Éxito o Fallo, dependiendo de lo que ocurrió:
Ahora, esto podría no verse muy diferente de lo que tiene ahora, pero considere cómo serían las cosas si tuviera más operaciones que pudieran resultar en una falla:
En cada una de las
yield
s de laprocess_file
función, si la llamada a la función devuelve un Fallo, laprocess_file
función saldrá, en ese punto , devolverá el valor de Fallo de la función fallida, en lugar de continuar por el resto y devolver elSuccess("All ok.")
¡Ahora, imagina hacer lo anterior con
if
s anidados ! (¿Cómo manejarías el valor de retorno?)Conclusión
Las mónadas son buenas :)
Notas:
No soy un programador de Python: utilicé la biblioteca de mónada vinculada anteriormente en un script que ninja había usado para la automatización de algunos proyectos. Sin embargo, deduzco que, en general, el enfoque idiomático preferido es utilizar excepciones.
IIRC hay un error tipográfico en el script lib en la página vinculada, aunque se me olvida dónde está ATM. Actualizaré si lo recuerdo.Me diff'd mi versión contra la página de y encontré:def failable_monad_examle():
->def failable_monad_example():
- lap
deexample
que faltaba.Para obtener el resultado de una función decorada Failable (como
process_file
), debe capturar el resultado en ayvariable
hacer unvariable.value
para obtenerlo.fuente
Una función es un contrato, y su nombre debe sugerir qué contrato cumplirá. En mi humilde opinión, si lo nombra
remove_file
así que debería eliminar el archivo y no hacerlo debería causar una excepción. Por otro lado, si lo nombratry_remove_file
, debería "intentar" eliminar y devolver boolean para saber si el archivo se ha eliminado o no.Esto llevaría a otra pregunta: ¿debería ser
remove_file
otry_remove_file
? Depende de su sitio de llamadas. En realidad, puede tener ambos métodos y usarlos en diferentes escenarios, pero creo que eliminar el archivo per-se tiene una alta probabilidad de éxito, por lo que prefiero tener soloremove_file
esa excepción de lanzamiento cuando falla.fuente
En este caso particular, puede ser útil pensar por qué es posible que no pueda eliminar el archivo. Digamos que el problema es que el archivo puede o no existir. Entonces debería tener una función
doesFileExist()
que devuelva verdadero o falso, y una funciónremoveFile()
que simplemente elimine el archivo.En su código, primero verificará si el archivo existe. Si es así, llame
removeFile
. Si no, entonces haz otras cosas.En este caso, es posible que aún desee
removeFile
lanzar una excepción si el archivo no se puede eliminar por algún otro motivo, como los permisos.Para resumir, se deben lanzar excepciones para cosas que son, bueno, excepcionales. Entonces, si es perfectamente normal que el archivo que está tratando de eliminar no exista, entonces eso no es una excepción. Escriba un predicado booleano para verificar eso. Por otro lado, si no tiene los permisos de escritura para el archivo, o si está en un sistema de archivos remoto que de repente es inaccesible, esas podrían ser condiciones excepcionales.
fuente