Source: I've worked in several "too large" codebases. C#, ASP, Ruby. I thought Visual Studio + C# + Resharper was an extremely potent combination. Despite that I prefer the Ruby ecosystem these days.
One glaring issue I often see overlooked is this: in nearly any project, most complexity and runtime errors tend to come from external data sources: databases, external APIs, user input, etc. The godlike powers of static languages and advanced IDEs don't help you so much there. So in practical reality for most projects this greatly levels the playing field between dynamic and statically languages.
> I can't see how this can work at scale in a large
> project without compile-time typesafety.
Moving from C# to Ruby, I thought compile-time type safety was going to be the biggest issue. It has been extremely close to a non-issue. This is going to sound almost absurdly low-tech but the answer for me is literally just "use descriptive identifier names" which is something you should do anyway. Method/function names should strongly imply the return type, ie "get_user_ids()" and not "get_stuff()". This makes most type snafus glaringly obvious; "current_temperature_centigrade = get_user_uids()" is glaringly wrong.
> I suppose 100% test coverage could provide some of the benefits
Yeah, that's part of the idea. The idea is that you should have close to 100% test coverage anyway and your tests are going to blow up if you screw up your types.
> I rely heavily on my IDE to tell me where a field is used
If you have nice descriptive identifier names (see above - something you should be doing regardless of dynamic typing) then grep or silver searcher is usually far superior in speed and functionality to any IDE's built in "find in files" IME. And your grep-fu (or ag-fu) is going to be useful in all kinds of situations. When feasible, master tools that will serve you
for life instead of getting good at some IDE you'll ditch in a few years.
> or to allow me to rename a method and still be confident that
> the callers have been updated
This is definitely something you give up in a dynamic environment, at least the auto rename part. However, the finding part is pretty well handled (see above).
> If you've worked on a large Python or JS codebase, what was
> it like to work in?
For Ruby, at least, here are my conclusions.
1. You do make some sacrifices as discussed above.
2. In exchange you get a massive gain thanks to being able to work with, prototype, and debug live code in the Ruby console. I have found this to be more than worth the sacrifices. Also in my experience people who don't like Ruby generally are people who have not taken advantage of this and therefore only experience the downsides.
3. Overly large monoliths are hell in any language, dynamic or not. The flaws of a dynamic language are exacerbated in overly large codebases, but I've seen some sheer monolith hell in static languages too.
4. Sticking to a well worn paradigm e.g. MVC alleviates a lot of pain regardless of typed vs. untyped.