Tests are how one constrains dynamically typed programs to make sure they work as expected. I have found that adding tests to untyped python code is far better at exposing bugs than adding typing is. I like my IDE to do completion too but I tend to add typing where I think it helps most rather than everywhere.
It just seems that some people like static types and good for them. Dynamic languages are for those of us who are, perhaps, lazy and want to be free to think about how to solve the problem at a slightly higher level without having to get into details.
For me the details activate my perfectionism and that blocks me from advancing with thinking about the goal and the effort of creating an elegant set of types makes me less willing to suddenly throw everything away when I realise there's a better approach. Dynamic languages give me that ability to "sketch" until I have an answer and at that point I have to ask: do I really need this to be in a higher performance language? Once or twice the answer has been yes but working out the approach in a dynamic language allowed me to do it right in the static language first time.
Type checks are proofs across all inputs. Tests are proofs across a given set of inputs. They are complimentary. If you want really robust code, use both.
I've generally found the complement to be true as well: once you have decent type modeling, tests don't find very much, if anything. I've primarily only seen modeling done well in Scala though.
Agree on the importance of testing. Among the production-grade codebases I've worked on, I've found that the dynamically-typed ones have more comprehensive testing. It's just so easy to think that a successful compile means your code will work. I've also found that it's harder to set up great test systems for static languages because you often have to modify your logic just to make it testable (looking at you, IoC). A delightful test system is one that engineers will use during development because it saves time, not an afterthought. For whatever reason, I haven't ever found something that provides this type of experience out of the box. In one organization, we spent months building a rig and achieved true TDD. The result was fewer production issues, faster development, and of course, better test coverage numbers. We eventually switched from javascript to typescript, but it didn't compare to the difference that test system made.
Sure but nobody ever writes tests that are as comprehensive at checking the basic types of stuff as simply adding static type hints (which is very easy if you start with them).
How many times have you seen a NoneType error or a KeyError? It's probably the most common class of bugs in Python and there's an easy way to eliminate it. And as a nice side effect your code is way easier to understand, navigate and edit.
I still sometimes have to give up and use Any in Python, partly because half the Python community haven't realised their mistake yet. But that's fine. It's still way better.
I took over maintenance of a big untestable program in Python once (a build system). None of it was designed for testing and every change I made would trigger some bug. The cost of running the whole program was about an hour of building so every tiny problem was incredibly frustrating.
I thought adding types might help to remove the most trivial mistakes and help me get it into shape faster. All I can say is that it didn't. The easiest mistakes to make were not the kind of things that triggered typing issues.
When I bit the bullet and found some things that could be made testable - only then did the burden start to fall and to sort of exponentially disappear. Each test, crappy and incomplete as it might have been, made it easier to refactor so that the next test could be added. I went from system tests inwards towards unit test because this, again, had the biggest benefit/cost.
So my thesis is that big general tests do more for you at the start and honing in towards more detailed ones returns less and less value. In this model I see types as being fairly detailed level tests so they can happen later on. This is a facetious, throwaway idea that just occurred to me so don't take it too seriously.
Comments
Tests are how one constrains dynamically typed programs to make sure they work as expected. I have found that adding tests to untyped python code is far better at exposing bugs than adding typing is. I like my IDE to do completion too but I tend to add typing where I think it helps most rather than everywhere.
It just seems that some people like static types and good for them. Dynamic languages are for those of us who are, perhaps, lazy and want to be free to think about how to solve the problem at a slightly higher level without having to get into details.
For me the details activate my perfectionism and that blocks me from advancing with thinking about the goal and the effort of creating an elegant set of types makes me less willing to suddenly throw everything away when I realise there's a better approach. Dynamic languages give me that ability to "sketch" until I have an answer and at that point I have to ask: do I really need this to be in a higher performance language? Once or twice the answer has been yes but working out the approach in a dynamic language allowed me to do it right in the static language first time.
Type checks are proofs across all inputs. Tests are proofs across a given set of inputs. They are complimentary. If you want really robust code, use both.
As a general rule of thumb, once you have proper tests, static typing doesn't find very much, if anything.
I've generally found the complement to be true as well: once you have decent type modeling, tests don't find very much, if anything. I've primarily only seen modeling done well in Scala though.
complementary
Agree on the importance of testing. Among the production-grade codebases I've worked on, I've found that the dynamically-typed ones have more comprehensive testing. It's just so easy to think that a successful compile means your code will work. I've also found that it's harder to set up great test systems for static languages because you often have to modify your logic just to make it testable (looking at you, IoC). A delightful test system is one that engineers will use during development because it saves time, not an afterthought. For whatever reason, I haven't ever found something that provides this type of experience out of the box. In one organization, we spent months building a rig and achieved true TDD. The result was fewer production issues, faster development, and of course, better test coverage numbers. We eventually switched from javascript to typescript, but it didn't compare to the difference that test system made.
Sure but nobody ever writes tests that are as comprehensive at checking the basic types of stuff as simply adding static type hints (which is very easy if you start with them).
How many times have you seen a NoneType error or a KeyError? It's probably the most common class of bugs in Python and there's an easy way to eliminate it. And as a nice side effect your code is way easier to understand, navigate and edit.
I still sometimes have to give up and use Any in Python, partly because half the Python community haven't realised their mistake yet. But that's fine. It's still way better.
I took over maintenance of a big untestable program in Python once (a build system). None of it was designed for testing and every change I made would trigger some bug. The cost of running the whole program was about an hour of building so every tiny problem was incredibly frustrating.
I thought adding types might help to remove the most trivial mistakes and help me get it into shape faster. All I can say is that it didn't. The easiest mistakes to make were not the kind of things that triggered typing issues.
When I bit the bullet and found some things that could be made testable - only then did the burden start to fall and to sort of exponentially disappear. Each test, crappy and incomplete as it might have been, made it easier to refactor so that the next test could be added. I went from system tests inwards towards unit test because this, again, had the biggest benefit/cost.
So my thesis is that big general tests do more for you at the start and honing in towards more detailed ones returns less and less value. In this model I see types as being fairly detailed level tests so they can happen later on. This is a facetious, throwaway idea that just occurred to me so don't take it too seriously.