Creo que se está combinando cuando se comprueban los tipos con la forma en que se comprueban. La tipificación en tiempo de ejecución no es necesariamente débil.
La principal ventaja de los tipos estáticos es exactamente lo que dices: son exhaustivos. Puede estar seguro de que todos los sitios de llamadas se ajustan al tipo simplemente dejando que el compilador lo haga.
La principal limitación de los tipos estáticos es que están limitados en las restricciones que pueden expresar. Esto varía según el idioma, con la mayoría de los idiomas que tienen sistemas de tipo relativamente simple (c, java) y otros con sistemas de tipo extremadamente poderoso (haskell, cayena).
Debido a esta limitación, los tipos por sí solos no son suficientes. Por ejemplo, en Java los tipos están más o menos restringidos a la verificación de nombres de tipos coincidentes. Esto significa que el significado de cualquier restricción que desee comprobar debe codificarse en un esquema de nombres de algún tipo, de ahí la gran cantidad de indirecciones y placas de calderas comunes al código de Java. C++ es un poco mejor ya que las plantillas permiten un poco más de expresividad, pero no se acerque a lo que puede hacer con los tipos dependientes. No estoy seguro de cuáles son las desventajas de los sistemas de tipo más potentes, aunque es evidente que debe haber alguna o más personas que los utilicen en la industria.
Incluso si está usando el tipado estático, es probable que no sea lo suficientemente expresivo para verificar todo lo que le importa, por lo que también tendrá que escribir pruebas.Si el tipado estático te ahorra más esfuerzo de lo que requiere en repetitivo es un debate que se ha desatado durante siglos y que no creo que tenga una respuesta simple para todas las situaciones.
En cuanto a su segunda pregunta:
¿Cómo podemos re-factor de forma segura en un lenguaje escrito en tiempo de ejecución?
La respuesta son las pruebas. Sus pruebas tienen que cubrir todos los casos que importan. Las herramientas pueden ayudarlo a medir cuán exhaustivas son sus pruebas. Las herramientas de comprobación de cobertura le permiten saber si las líneas de código están cubiertas por las pruebas o no. Las herramientas de prueba de la mutación (bufón, heckle) pueden indicarle si sus pruebas son lógicamente incompletas. Las pruebas de aceptación le permiten saber qué ha escrito y cumple los requisitos, y, por último, las pruebas de regresión y rendimiento garantizan que cada versión nueva del producto mantenga la calidad de la última.
Una de las mejores cosas de contar con pruebas adecuadas en lugar de depender de complejas indirecciones de tipo es que la depuración es mucho más simple. Al ejecutar las pruebas, se obtienen aserciones fallidas específicas dentro de las pruebas que expresan claramente lo que están haciendo, en lugar de declaraciones obtusas de error del compilador (piense en errores de plantilla de C++).
No importa qué herramientas use: escribir código en el que confíe requerirá esfuerzo. Lo más probable es que requiera escribir muchas pruebas. Si la penalización por errores es muy, como el software de control aéreo o espacial, es posible que necesite utilizar métodos matemáticos formales para probar el comportamiento de su software, lo que hace que dicho desarrollo sea extremadamente costoso.
Aún podría refactorizar, solo usaría pruebas unitarias como reemplazo del IDE/compilador. Realmente no es * tan * malo una vez que te acostumbras. –