You do not have to add "guards" to every function, just the functions publicly available via API. And you only need to add them when making (breaking) changes, like adding more parameters, but most of the time you can figure out what the caller wants to do and keep your code backwards compatible.
Also you should wait until your API is somewhat stable before adding the guards. So for most code, you do not need guards. But if your code is used by many, that defensive coding/guards, taking only a few minutes to add, will save countless man-hours that would otherwise be spent debugging.
As a general rule I like errors to throw early. So when I found a bug, (I first write a test to automatically reproduce the bug, then) I backtrack and add guards to each step (with helpful debug/data in the error message), so that the bug would be caught at the surface, rather then causing weird issues several layers down.
And guards are much easier to write then complicated type definitions. And the errors will be more informative, helpful and human friendly then errors from a type-checker.
Defensive coding is mostly useful in long living apps that have a lot of state, and which is constantly developed (new features added, breaking changes, etc). You would not need defensive code in programs that are executed once and then thrown away.
Also you should wait until your API is somewhat stable before adding the guards. So for most code, you do not need guards. But if your code is used by many, that defensive coding/guards, taking only a few minutes to add, will save countless man-hours that would otherwise be spent debugging.
As a general rule I like errors to throw early. So when I found a bug, (I first write a test to automatically reproduce the bug, then) I backtrack and add guards to each step (with helpful debug/data in the error message), so that the bug would be caught at the surface, rather then causing weird issues several layers down.
And guards are much easier to write then complicated type definitions. And the errors will be more informative, helpful and human friendly then errors from a type-checker.
Defensive coding is mostly useful in long living apps that have a lot of state, and which is constantly developed (new features added, breaking changes, etc). You would not need defensive code in programs that are executed once and then thrown away.