I actually was aware of the autocomplete benefit before. I’ve only spent about three months using a staticly typed language (TypeScript). In that time I found myself not using autocomplete too much for whatever reason, but I suspect that this is more the exception than the rule, that autocomplete is usually something that people find useful.
I wasn’t aware of those benefits for refactoring! That’s so awesome! If it’s actually as straightforward as you’re saying it is, then I see that as a huge benefit of static typing, enough where my current position is now that you’d be leaving a lot on the table if you don’t use a staticly typed language for all but the smallest of projects.
At work we actually decided, in part due to my pushing for it, to use JavaScript instead of TypeScript for our AWS Lambda functions. Those actually seem to be a time when you can depend on the codebase being small enough where static typing probably isn’t worth it.
Anyway, important question: were you exaggerating at all about the refactoring points?
No. I compulsively use the refactor/rename operation (cntrl-shift-r in my own Visual Studio setup) probably 4 or 5 times in a given coding session on my personal Unity project, and trust that all the call sites got fixed automatically. I think this has the downstream effect of having things become a lot more intelligible as my code grows and I start forgetting how particular methods that I wrote work under-the-hood.
Find-all-usages is also extremely important when I’m at work; just a couple weeks ago I was changing some authentication logic for a database we used, and needed to see which systems were using it so I could verify they all still worked after the change. So I just right-click and find-usages and I can immediately evaluate everywhere I need to fix.
As an aside, I suspect a lot of the critiques of statically-typed languages come from people whose static typing experiences come from C++ and Java, where the compiler isn’t quite smart enough to infer most things you care about so you have to repeat a whole bunch of information over and over. These issues are greatly mitigated in more modern languages, like C# (for .NET) and Kotlin (for the JVM), both of which I’m very fond of. Also: I haven’t programmed in Java for like three years, so it’s possible it has improved since I touched it last.
Full disclosure: pretty much all my experiences the last few years have been of statically-typed languages, and my knowledge of the current dynamic language landscape is pretty sparse. All I can say is that if the dynamic language camp has found solutions to the refactor/rename and find-all-usages problems I mentioned, I am not aware of them.
You can get some of these benefits from optional/gradual typing systems, like with Typescript; the only thing is that if it’s not getting used everywhere you get a situation where such refactorings go from 100% to 90% safe, which is still pretty huge for discouraging refactoring in a beware-trivial-inconveniences sense.
I compulsively use the refactor/rename operation (cntrl-shift-r in my own Visual Studio setup) probably 4 or 5 times in a given coding session on my personal Unity project, and trust that all the call sites got fixed automatically.
Similar here. I use renaming less frequently, but I consider it extremely important to have a tool that allows me to automatically rename X to Y without also renaming unrelated things that also happen to be called X.
With static typing, a good IDE can understand that method “doSomething” of class “Foo” is different from method “doSomething” of class “Bar”. So I can have automatically renamed one (at the place it is declared, and at all places it is called) without accidentally renaming the other. I can automatically change the order of parameters in one without changing the other.
It is like having two people called John, but you point your cursor at one of them, and the IDE understands you mean that one… instead of simply doing textual “Search/Replace” on your source code.
I have never had a static type checker (regardless of how sophisticated it is) help me prevent anything more than an obvious error (which should be caught in testing anyway).
In other words, every static type declaration is a unit test you didn’t have to write… that is, assuming we are talking about automated testing here; are we?
I agree that when you are writing new code, you rarely make type mistakes, and you likely catch them immediately when you run the program. But if you use a library written by someone else, and it does something wrong, it can be hard to find out what exactly went wrong. Suppose the first method calls the second method with a string argument, the second method does something and then passes the argument to the third method, et cetera, and the tenth method throws an error because the argument is not an integer. Uhm, what now? Was it supposed to be a string or an integer when it was passed to the third or to the fifth method? No one knows. In theory, the methods should be documented and have unit tests, but in practice… I suspect that complaining about having to declare types correlates positively with complaining about having to write documentation. “My code is self-documenting” is how the illusion of transparency feels from inside for a software developer.
I mostly use Java, and yes it is verbose as hell, much of which is needless. (There are attempts to make it somewhat less painful: Lombok, var.) But static typing allows IDE to give me all kinds of support, while in dynamically typed languages it would be like “well, this variable could contain anything, who knows”, or perhaps “this function obviously expects to receive an associative array as an argument, and if you spend 15 minutes debugging, you might find out which keys that array is supposed to have”. In Java, you immediately see that the value is supposed to be e.g. of type “Foo” which contains integer values called “m” and “n”, and a string value called “format”, whatever that may mean. Now you could e.g. click on their getters and setters, and find all places in the program where the value is assigned or read (as opposed to all places in the program where something else called “m”, “n”, or “format” is used). That is a good start.
It is like having two people called John, but you point your cursor at one of them, and the IDE understands you mean that one… instead of simply doing textual “Search/Replace” on your source code.
Thanks for explaining this. I had been planning on investigating how it compares to search and replace, but I think this clarified a lot for me.
And thank you for the rest of your thoughts too. I think my lack of experience with static typing is making it hard for me to fully grok them, but I am groking them to some extent, and they do give me the vibe of being correct.
Thank you for those thoughts, they’re helpful.
I actually was aware of the autocomplete benefit before. I’ve only spent about three months using a staticly typed language (TypeScript). In that time I found myself not using autocomplete too much for whatever reason, but I suspect that this is more the exception than the rule, that autocomplete is usually something that people find useful.
I wasn’t aware of those benefits for refactoring! That’s so awesome! If it’s actually as straightforward as you’re saying it is, then I see that as a huge benefit of static typing, enough where my current position is now that you’d be leaving a lot on the table if you don’t use a staticly typed language for all but the smallest of projects.
At work we actually decided, in part due to my pushing for it, to use JavaScript instead of TypeScript for our AWS Lambda functions. Those actually seem to be a time when you can depend on the codebase being small enough where static typing probably isn’t worth it.
Anyway, important question: were you exaggerating at all about the refactoring points?
No. I compulsively use the refactor/rename operation (cntrl-shift-r in my own Visual Studio setup) probably 4 or 5 times in a given coding session on my personal Unity project, and trust that all the call sites got fixed automatically. I think this has the downstream effect of having things become a lot more intelligible as my code grows and I start forgetting how particular methods that I wrote work under-the-hood.
Find-all-usages is also extremely important when I’m at work; just a couple weeks ago I was changing some authentication logic for a database we used, and needed to see which systems were using it so I could verify they all still worked after the change. So I just right-click and find-usages and I can immediately evaluate everywhere I need to fix.
As an aside, I suspect a lot of the critiques of statically-typed languages come from people whose static typing experiences come from C++ and Java, where the compiler isn’t quite smart enough to infer most things you care about so you have to repeat a whole bunch of information over and over. These issues are greatly mitigated in more modern languages, like C# (for .NET) and Kotlin (for the JVM), both of which I’m very fond of. Also: I haven’t programmed in Java for like three years, so it’s possible it has improved since I touched it last.
Full disclosure: pretty much all my experiences the last few years have been of statically-typed languages, and my knowledge of the current dynamic language landscape is pretty sparse. All I can say is that if the dynamic language camp has found solutions to the refactor/rename and find-all-usages problems I mentioned, I am not aware of them.
You can get some of these benefits from optional/gradual typing systems, like with Typescript; the only thing is that if it’s not getting used everywhere you get a situation where such refactorings go from 100% to 90% safe, which is still pretty huge for discouraging refactoring in a beware-trivial-inconveniences sense.
Similar here. I use renaming less frequently, but I consider it extremely important to have a tool that allows me to automatically rename X to Y without also renaming unrelated things that also happen to be called X.
With static typing, a good IDE can understand that method “doSomething” of class “Foo” is different from method “doSomething” of class “Bar”. So I can have automatically renamed one (at the place it is declared, and at all places it is called) without accidentally renaming the other. I can automatically change the order of parameters in one without changing the other.
It is like having two people called John, but you point your cursor at one of them, and the IDE understands you mean that one… instead of simply doing textual “Search/Replace” on your source code.
In other words, every static type declaration is a unit test you didn’t have to write… that is, assuming we are talking about automated testing here; are we?
I agree that when you are writing new code, you rarely make type mistakes, and you likely catch them immediately when you run the program. But if you use a library written by someone else, and it does something wrong, it can be hard to find out what exactly went wrong. Suppose the first method calls the second method with a string argument, the second method does something and then passes the argument to the third method, et cetera, and the tenth method throws an error because the argument is not an integer. Uhm, what now? Was it supposed to be a string or an integer when it was passed to the third or to the fifth method? No one knows. In theory, the methods should be documented and have unit tests, but in practice… I suspect that complaining about having to declare types correlates positively with complaining about having to write documentation. “My code is self-documenting” is how the illusion of transparency feels from inside for a software developer.
I mostly use Java, and yes it is verbose as hell, much of which is needless. (There are attempts to make it somewhat less painful: Lombok, var.) But static typing allows IDE to give me all kinds of support, while in dynamically typed languages it would be like “well, this variable could contain anything, who knows”, or perhaps “this function obviously expects to receive an associative array as an argument, and if you spend 15 minutes debugging, you might find out which keys that array is supposed to have”. In Java, you immediately see that the value is supposed to be e.g. of type “Foo” which contains integer values called “m” and “n”, and a string value called “format”, whatever that may mean. Now you could e.g. click on their getters and setters, and find all places in the program where the value is assigned or read (as opposed to all places in the program where something else called “m”, “n”, or “format” is used). That is a good start.
Thanks for explaining this. I had been planning on investigating how it compares to search and replace, but I think this clarified a lot for me.
And thank you for the rest of your thoughts too. I think my lack of experience with static typing is making it hard for me to fully grok them, but I am groking them to some extent, and they do give me the vibe of being correct.
Good to know. Thanks!