Static Type CheckingSource: comp.lang.smalltalkDate: 17-Sep-99
Problem: Static type checking is aimed to ensure that certain run- time errors will never occur: invoking a method on an object that does not support it (for example, telling a modem driver to draw itself on the screen). What can be wrong with that?
Dave Harris explained: Firstly, it complicates the language enormously. For example, static languages typically need multiple inheritance and genericity; dynamic languages typically don't. There is no consensus on how best to do these things. Java's "interfaces" are controversial, and there are at least 3 different proposals on how to add genericity to that language. In C++ we have around 6 different kinds of type-cast. A 3 keywords were needed just to bring immutability into the type system (const, const_cast, mutable). Eiffel has covariance, selective export and needed a global analysis to check its type-safety. And so forth. Static type systems are hard. Secondly, it makes programs much more verbose. This is partly because of all those manifest type declarations. You can see it clearly with Java's anonymous inner classes: fileCollection.openAll( new OpenAllFiles { InputStream open( final String filename ) throws FileNotFoundException { return new FileInputStream( filename ); } } );We have to declare the types of arguments, the return result and any exceptions thrown. The type declarations out-weigh the code that does the work. Verbosity also comes from the need to write multiple versions of code to establish or preserve type distinctions. For example, the C++ std::vector has two versions of begin(), one returning iterator and one returning const_iterator. These distinctions are often not relevant to much of the code that must preserve them. Thirdly, manifest types introduce spurious impedance mis-matches - they make classes incompatible which would otherwise be compatible. This gets in the way of code reuse. In real Java people don't write code like openAll() above, because the type "function accepting String returning InputStream throwing FileNotFoundException" is too specific to be reusable. It's harder to integrate 3rd party libraries which don't know about each other because their type declarations over- determine their interfaces. Fourthly, manifest types require you to hardwire design knowledge into the code at a very early stage. Sometimes this requires knowledge you just don't have. For example, it's hard to write a general proxy class in Java because you just don't know what protocol you need to declare it to have. In Smalltalk it's trivial. Even when you do know, putting the knowledge into the code so early often amounts to a premature commitment. For example, why did I use String above instead of File? Either would have worked. It would be better to defer the decision until I know more about how the filenames are going to be used. Let the design solidify in its own time. Fifthly, manifest types make source code harder to change. For example, if I do decide to use File instead of String for the names in FileCollection, I have to update not just FileCollection itself but OpenAllFiles and all the client code that uses it. The changes ripple through the source. Refactoring becomes more work. Sometimes, if I don't have access to the client code, it can't be done at all. Type conformance is especially rigid and fragile when it must follow the inheritance hierarchy. Often in C++ I am told, "You just inherit from this class to use this feature". Certain kinds of change become flatly impossible. Finally, what does all this buy you? Static type checking is not a magic bullet. You can still have bugs, from division by zero and null pointer indirection upwards. You still need to test your code. None of this is to say that static typing isn't worth it. But consider: language complexity leads to bugs. Verbosity creates opportunities for bugs. Many of the "errors" caught by static type systems are actually caused by impedance mis-matches in the type-system itself. The difficulty of dealing with static type checking takes resources away from other activities. Vendors spend time working on things like multiple inheritance and genericity when they could be working on tools like integrated development environments, profilers, automated testers, refactoring browsers. The extra complexity of the language makes such tools harder to write. Programmers put their effort into expressive type declarations instead of writing unit tests. They don't refactor because it's too hard, so crud persists. Compile times are longer because the compiler needs to see the full interface of every variable. Change a class, and the static checks must be revalidated for every part of the system that uses. Everything drags. It all mitigates against code quality. Personally I have mixed feelings about all this. Most of my work is with C++ and Java. I strongly believe in the advantages of static type checking. Most of the problems above are to do with making types manifest, explicitly and redundantly declared in the source code, rather than latent and deduced by the compiler. Languages like Haskell manage to be statically type-checked and yet avoid the verbosity. There are also languages like Cecil and Dylan which make type declarations optional, so you can add them late in the design if/when needed. I'm glad people are researching into this stuff; I don't think static checking should be written off altogether. [... And what about all the bugs eliminated with type-checking? ...] You have to test anyway, because there are classes of bugs that static type checking doesn't find. Put more effort into it. Use tools to make it easier. Write test suites before you write the code they test. Routinely ensure 100% code coverage. Use the debugger as your IDE. All the resources that you used to put into static type checking, put into testing instead. Smalltalkers claim that you get fewer bugs over-all if you take this route. [...] I think the issue here is "value for effort". Conventional static type checking takes big effort and delivers small gain, especially on top of Smalltalk-like testing. Some people believe programming is so hard that almost any gain is worth the effort. Other people believe the available effort is limited and want to maximize the value before they run out. Or to put it another way, their process and effort leads to commercial-level quality and further effort isn't worth it. More Info: Kent Beck, Simple Smalltalk Testing: With Patterns WikiWikiWeb, There Are No Types
|