This post originated from an RSS feed registered with Agile Buzz
by Keith Ray.
Original Post: Type Checking
Feed Title: MemoRanda
Feed URL: http://homepage.mac.com/1/homepage404ErrorPage.html
Feed Description: Keith Ray's notes to be remembered on agile software development, project management, oo programming, and other topics.
You might think it's obvious that compile-time type-checking is valuable for preventing mistakes. For example, David Warnock wrote "obvious I think, if the language cannot tell that x is a string or an integer then lots of easy mistakes are not caught early". Now I've done most of my programming in compile-time type-checked languages (C, C++, Java, Pascal, Object Pascal), and I'm comfortable with them, though sometimes I find them overly verbose and tightly-coupled. A problem with the tight coupling is when you change something from an 'int' to a 'long int' deep inside a class somewhere, and the change ripples across from header file to header and source files until you've had to change half a dozen files.
Unfortunately for we who are using C and C++, static type checking is broken for certain basic types, in order to be compatible with older C source code. For example, C/C++ can't tell the difference between a string or an integer in many cases, because 'char *' converts to 'int' silently. That's actually a problem with a string represented as a character-array, but some string classes (thankfully, not std::string) have a 'conversion operator' that returns a 'char *' and the same silent conversion problem can happen with those string classes. 'bool' also converts to/from 'int' silently.
Now the dynamic-typing people say that, in practice, it's very rare to pass a string into a function that expects an integer, or vice versa. (I have rarely experienced such a problem in C++ when trying to do manual refactoring, but that was quickly detected via crashes when running tests and the application.) However, there is a big loop-hole of type-checking in any language that has polymorphism, whether dynamically-typed or staticly-typed: your functions may require arguments whose type implement a certain interface (or be a subclass of a certain class), but there is nothing that forces the objects being passed into those functions to implement anything meaningful. In practice, this isn't a big problem - in fact, it happens so rarely that static type-checking advocates don't even think of this problem.
By the way, Eiffel allows you to implement inherited assertions (invariants and pre- and post- conditions); useful if you're very serious about program correctness. There are arguments that extensive unit tests (and test-driven development) eliminate the need for the Eiffel-style assertions. I'm not entirely convinced. I find that assertions are good for documenting 'negative' facts like "argument x must not be null" and unit tests are good for documenting 'positive' facts like "this function returns 12 if the arguments are 4 and 2." There have been several occasions where a failing "precondition" assertion alerted me to problems in my usage of a class in test-driven development.
Try out Objective-C: it has optional type-checking. To turn off type-checking, declare variables as type 'id', and/or declare only classes (and their member data) and class-methods in your header files, and implement the instance methods in the source file without declaring them in the header. Since the instance methods aren't declared in the header file, they won't be checked where you call them. (Some programmers may be doing this as a way of keeping "private" methods a little more private.) If you encounter an error because of the lack of compile-time type-checking, then declare the variables with the appropriate class pointer types instead of 'id', and put instance method declarations in the header files.
To sum up: arguments for compile-time type-checking ignore some realities: some popular languages, like C/C++ have weak type-checking for certain types, but in practice, there are few problems with those types, and the problems are usually detected when the code is tested. Most languages that have polymorphism also weaken compile-type type-checking, because they only guarantee that methods exist and have the right interface/base-class types of their arguments, but do not guarantee the methods do the right thing; again only testing confirms that you really have the right types. C/C++, Java, and some other languages also weaken compile-time type-checking by allowing type-casting, and the compiler in those languages can't guarantee that an object (pointer) variable isn't null / null-pointer - a null pointer is never the right "type" if you're trying to call methods on it. (Don't you just hate crashes and NullPointerExceptions? Is your favorite language preventing those?)