Note: I've not yet done any serious web-development, mostly command line tools, which I realise is not DHH's main focus.
This is what Erlang has and it’s very convenient since once a design is fixed, I end up writing a spec to remind me what types the function expects.
@spec foo(String.t()) :: String.t()
def foo(bar)
is better than def foo(String.t() bar): String.t()Thorough tests of the behavior of your system (which should be done whether the language is dynamic or not) catch the vast, vast majority of type errors. "More runtime errors" in a well designed codebase don't mean errors for the user - it means tests catch them
Seriously.. the secret to writing great dynamic code is getting very good at testing
I see dynamic codebases being written differently though. We all know those tradeoffs - so conventions pop up to deal with them. A method that ends in a ? in ruby - like user.admin? - always returns a boolean. You get better at naming to make types clearer
I definitely miss the perfect automated refactorings though
And that was even before someone wrote DOOM in TS's type system.