Herramientas de chequeo estático en python, innecesarias?

Actualmente en el repositorio de la herramienta llamada mypy lei un comentario donde se dejaba clarísimo que la herramienta no le gustaba, pero además mencionaba que este tipo de herramientas no era necesario (con palabras más fuertes) debido a que  python es un lenguaje que no requiere declarar en el código el tipo de dato. El fin de este post no es entrar en una polémica sobre static vs dynamic, mas bien es intentar demostrar cuales son los beneficios de herramientas como mypy.

Primero hay que entender el contexto de python, pero no sin antes  empezar citando una de las encuesta de jetbrains donde el mayor deseo de los python devs es static typing ver Figura 1 (aunque no se preocupen esto de que sea mandatory se ha mencionado ya y no va a suceder).

Figura 1: Lo que más desean los programadores en python

Python es un lenguaje multiparadigma, pero más que ello es multipropósito, como se ve en la Figura 2 es usado en una gran cantidad de sectores, por ello las necesidades en cada sector van a variar, por ejemplo en el sector de análisis de datos he visto una inmensa cantidad de notebooks y les puedo asegurar que la última preocupación aquí es definir tipos de datos, lo mismo sucede con herramientas de devops y automatización, estas últimas incluso son un único archivo (muchos vienen de bash) ahora cuando se entra a trabajar en software más robustos y grandes las necesidades cambian

Figura 2: Diferentes usos en diferentes áreas con python

Programadores vienen y van, el software perdura

Muchos programadores que trabajan en desarrollo de software, están en el producto desde el principio y eventualmente se van, al mismo tiempo otros programadores llegaran a darle soporte a ese software y así se convierte en un ciclo, hace unos meses incluso veíamos un aumento de empleos en cobol, un lenguaje que tiene 6 décadas y aún resiste, los programadores que llegaran a darle soporte a ese software que ya está en funcionamiento y que seguramente tendrá reporte de fallas o mejoras, que deberían hacer para entender el sistema, revisar el código? leer la documentación? reunirse con las personas que lo usan? cual de estas opciones llevará más tiempo y costará más? ver Figura 3 como se aprecia en la imagen para entender un software se debe invertir dinero y tiempo, a mayor proximidad más tiempo, la forma de entender un software más fácil (pero no necesariamente deber ser la única) está en el cuadrante superior izquierdo, como se logra apreciar, el codigo es uno de ellos, por lo tanto el código debe ser comunicativo con su intención

Figura 3: Costo y proximidad de métodos de comunicación. Obtenido del libro https://www.amazon.com/Robust-Python-Write-Clean-Maintainable/dp/1098100662

Intención del código

La intención del código es algo que he venido leyendo en varios libros, por ejemplo en el libro clean code este menciona la importancia de los nombres de las variables, nombre de los métodos y como el código está escrito, la intención del código es lo que se quiere lograr hacer, en general yo debería poder leer una función y entender que quiere hacer sin tener que leer todo el código de todo el proyecto (responsabilidad única), en el libro robust python se menciona que en una buena  comunicación del código, elegir la estructura correcta que recibe una función, y que retorna es fundamental, poder usar typing en python es indicar no solo que una variable es iterable es decir que podría ser list, tuple, dic y set, si no una estructura en específico, ayuda a crear una conexión fuerte entre tu intención y tu código

Ejemplos

def create_results_zip_file_list(results_directory: Path) -> List[str]:
    """Create a list of the .csv files in the provided results directory."""
    results_files_generator = results_directory.glob("*.csv")
    results_file_list = []
    for results_file in results_files_generator:
        results_file_list.append(str(results_file))
    return results_file_list
código obtenido de https://github.com/AnalyzeActions/WorkKnow

El código anterior es un claro ejemplo de cómo debería ser un código que quiere comunicar su intención, el single responsibility principle  esta clarisimo, no necesito ir a ver quien llama la función para entender qué hace  o qué tipo de datos debe recibir, y muy a pesar de que muchas veces python es criticado por que las herramientas opensource no tienen codigo muy facil de leer, esta es la otra cara de la moneda, entre muchas cosas además de sus nombres claros de variables tiene un claro uso del typing, usar typing hará sin duda más lento el desarrollo eso es claro, pero esto es una inversión, se programa pensando a futuro, veamos más ejemplos

def close_kitchen_if_past_cutoff_time(point_in_time):
 	if point_in_time >= closing_time():
 		close_kitchen()
 		log_time_closed(point_in_time)
Código obtenido del libro robust python

En el ejemplo anterior que es point_in_time? es la pregunta que me haría cuando yo quiera invocar la función, en este ejemplo sus variables comunican, su nombre es claro pero no se sabe exactamente que son los datos que se requieren, por ello se debe ir a revisar quien invoca la función previamente, lo cual hace que se desgaste tiempo encontrando que hace esta función y se entra en el cuadrante de la Figura 3 en busca de cómo trabaja el código, reuniones? chats? revisión de historial de git? preguntan a los involucrados?, los tipos de datos que se seleccionan comunican, por ejemplo si se tuviera un método que recibe un tipo de dato set en python deja en claro que no se recibirán datos repetidos , ahora que pasa si el código se cambia un poco

def close_kitchen_if_past_cutoff_time(point_in_time: datetime.datetime)->None:
 	if point_in_time >= closing_time():
 		close_kitchen()
 		log_time_closed(point_in_time)
Código obtenido del libro robust python

se entiende mejor, solo se agrego un type a la variable y ya se sabe que recibe y en este caso es un función que no retorna nada, por lo tanto el programador que deba trabajar con esta función sabrá que tipo de variable debe enviar y que retornara la función, veamos otro ejemplo

def generate_save_path(repository_csv: str, save_path: Path):
    """Generate each path that a repo should be saved to based on its name."""
    final_repository_paths = []
    converted_data = pd.read_csv(repository_csv)
    repository_list = converted_data["url"].tolist()
    for repo in repository_list:
        repo_name = os.path.splitext(os.path.basename(repo))[0]
        path = pathlib.Path.home() / save_path / repo_name
        final_repository_paths.append(str(path))

    # print(final_repository_paths)
    return final_repository_paths
Código obtenido de https://github.com/AnalyzeActions/ActionTraction/tree/main/action-traction

Como vimos en los ejemplos anteriores el código se hace mucho más fácil de entender a medida que cumple ciertos requisitos, entre ellos usar typing, sin embargo python no valida los typing en runtime, es decir que si se espera un tipo de variable en específico y se envía otra python no generará ningún error ni advertencia, por ello se utilizan herramientas como mypy para validar los tipos de datos en las invocaciones, pasada de parámetros e importación de módulos, incluso se puede integrar con infinidad de herramientas automatizan el trabajo, pre hooks, editores de código etc, un ejemplo de un archivo de configuración de mypy seria el siguiente

[mypy]

[mypy-pygments.*]
ignore_missing_imports = True

[mypy-IPython.*]
ignore_missing_imports = True

[mypy-commonmark.*]
ignore_missing_imports = True

[mypy-colorama.*]
ignore_missing_imports = True

[mypy-ipywidgets.*]
ignore_missing_imports = True
Codigo obtenido de https://github.com/willmcgugan/textual

con esto cuando se ejecuta mypy se pueden crear reglas para ignorar validaciones en algunos módulos que probablemente no utilizan typing, o no son necesarios, por lo tanto no se vuelve algo obligatorio y aburrido, por todo eso gracias a mypy he podido encontrar bugs a edad temprana y no dejarlos convertir en una bola de nieve

Conclusión

Python es un lenguaje que tiene espacio para todos, hay muchas áreas y proyectos y no siempre se va a requerir usar typing, en muchos temas de automatización cuando se trabaja al estilo scripting esto no se usa y además que puede no ser necesario, pero en proyectos grandes que trabajan gran cantidad de personas y constantemente está evolucionando, y al tener tantos cambios requiere tener código que sea fácil de leer y entender, como se explicó para ello es necesario utilizar muchas veces typing de datos y herramientas que permitan validar el código como es en este caso mypy