«« jid3rL with FramesGroup jid3rL is Building Nightly »»
blog header image
ClassCastExceptions are Useless?

James Robertson spotted something in comp.lang.smalltalk:

Just curious, but how many people out there using static languages actually try to pass invalid types to methods? How many Java users commonly get ClassCastExceptions at runtime?

My answers are:

* I never pass invalid types to methods.
* I have no memory of ever getting a ClassCastException in Java.

After using dynamic languages for a while I have come to the conclusion that static typing solves a problem that doesn't exist.

When using Java Collections I get ClassCastExceptions at runtime periodically, but it's usually because I'm wrapping the collection inside another class that has methods returning a specific type instead of just Object. When I retrieve the object from the collection I cast it to that type.

What's the real reason I get ClassCastExceptions? Because I refactor a lot. After a refactoring the wrapper class to use B instead of A you might be putting a type B into the collection and incorrectly casting it to the old A in a wrapper class method[1].

This problem will go away when generics are introduced and I can specify the type I want a collection to contain. The compiler will tell me I'm trying to put a B into collection<A> and I'll be forced to refactor it to collection<B>. Then the compiler will tell me I'm casting an item from collection<B> to an A (the place where the ClassCastException formerly occured for me). Actually Eclipse will tell me these things as I type, instead of at compile time[2].

As I understand it, and I could be way wrong as usual, languages like Smalltalk don't have this problem because they don't enforce type, so you never cast an object coming out of a collection (I assume there is a complementary group of classes to Java Collections in Smalltalk). If I were refactoring the same problem in Smalltalk, without the ClassCastException I may never know that I'm returning an object of the wrong type after the refactor (A instead of B). To catch this problem, you'll need good unit tests and you'll need to change them all from using A to B. You could always just nuke the A class, and the compiler will tell you it's invalid. With static typing in Java and generics, you don't have to nuke A.

[1] Note: this is different from putting in new type A' and incorrectly casting it to the old version of A. A refactoring IDE like Eclipse would change all of the references of A to A' and you wouldn't get ClassCastExceptions because A doesn't exist any more.

[2] Eclipse is compiling the code in the background as I type so I don't need to compile it after.

Update 11:41 am James responds:

You don't have this problem in Smalltalk because it's not the sort of problem you tend to get yourself into, period. Say I had a collection holding Foos. If I refactor, and I end up having a collection holding Bars (completely incompatible), then I have bigger problems. Even so, I would have had to refactor all the surrounding code that accesses the collection elements - and if I didn't have tests under those circumstances, I'm in trouble whether I have static typing or dynamic typing. To be brutal, if you trust the compiler to solve this for you, then you shouldn't be writing code.

I agree, testing is critical. If you're making up a new class you have to unit test it, and tests will catch refactoring errors from B to A in Java or Smalltalk.

I wouldn't trust the compiler to test the result of a refactoring for me any farther than I could throw it, but with static types and generics it will be a lot more obvious when I break something.

This probably seems like type hand-holding, but it's nice to see something break immediately as you type it, just like you expect it to -- even before you run the unit test suite. It's good immediate feedback.

If you were unable to unit test (as stupid as that sounds, most people still don't) would you rather use a dynamically or statically typed language? Would static typing help? Assume for this question you are equally familiar with Java and Smalltalk and neither has an advantage in that respect.

Personally I'm still in Java land because it's what I need to know -- at the very least -- to get a job. Java is in more places than Smalltalk or Python. I'll move on to Python or Smalltalk in a bit, and if I've used Java and know it well I'll be better informed about the differences between the languages. I still have faith that I will "see the light" in regards to dynamically typed languages. I was just stating a case for ClassCastException in Java.

Update 1:34 pm Ian Bicking says on his blog:

A tool can analyze statically typed code and say with some confidence exactly where a class or method is used -- in Smalltalk, Python, or other dynamically typed languages, refactoring is just a string match. Good naming practices can make that string search more reliable, but it's still just strings, not a fully type-annotated source.


This is one of those unfortunate places where you can't have it both ways. Dynamic typing and late binding is resistant to static analysis, and static analysis can be used for good things. (And Python is actually more resistant than most dynamic languages.)

Interesting!

Posted at August 10, 2004 at 09:01 AM EST
Last updated August 10, 2004 at 09:01 AM EST
Comments

Like Eclipse, Smalltalk environments have refactoring tools (they had them first, in fact). I don't have automated refactoring tools in C++, so I have to rely on the compiler to find problems involving renaming a class, variable or method, or retyping a method result or method argument (or templated container). Usually this works, but C++'s sometimes automatic type conversions like int = bool = pointer don't give me error messages for a type-change.

Please don't use the word "refactoring" for any changes that change the behavior of the software. "Refactoring" means "improving the design without changing the behavior". If a collection of A's has different behavior than a collection of B's or a collection of A's and B's, then you're rewriting, not refactoring.

» Posted by: keith ray at August 10, 2004 11:57 AM

The "refactoring" I referred to was a class wrapped around a collection of A, let's call it W. All of the methods in W used A, then I wanted to change them to use B.

So I guess the behaviour of W did change: it takes/returns B instead of A. This is not "refactoring W" in the correct sense, you're right.

If I had just changed the insides of A so that A is now A', then the behaviour of W would not have changed, since the change is encapsulated in A.

But if I'm doing all of that that inside an API without changing it, then I'm refactoring beneath the API, right?. In that sense I was refactoring my application, above. I guess it depends on your granularity level and what you refer to.

It's a good thing to watch out for though, thanks Keith.

» Posted by: Ryan at August 10, 2004 12:14 PM

See
http://nice.sourceforge.net/safety.html#id2441593

» Posted by: Isaac Gouy at August 10, 2004 02:00 PM

It is truly baffling that refactorings and indeed the very idea and first implementation of a refactoring browser all come from Smalltalk where, as you say, "refactoring is just a string match". I wonder how that could be? I wonder if you could be wrong about all the advantages of static type information for refactoring, and about how in Smalltalk you only have a string match?

» Posted by: at August 10, 2004 02:45 PM

"refactoring is just a string match"
See the comments on Ian Bickings blog

» Posted by: Isaac Gouy at August 10, 2004 06:15 PM

Aren't generics already supported? or are you just running your jvm a few versions behind the current?

» Posted by: Kibbee at August 11, 2004 08:21 AM

Generics are supported in Java 5 (1.5), which is still in beta.

http://java.sun.com/j2se/1.5.0/

» Posted by: Ryan at August 11, 2004 08:23 AM

still in beta, damn they move slow. i think i heard about that 6 months ago

» Posted by: Kibbee at August 11, 2004 06:33 PM

Incidentally, the parameteric types (generics) in Nice don't require a new JVM, and local type inference reduces the verbiage:

let list = new LinkedList();
list.add(0);
let x = list.iterator.next;

or

let List<int> list = new LinkedList();
list.add(0);
let x = list.iterator.next;

instead of

List<Integer> list = new LinkedList<Integer>();
list.add(new Integer(0));
Integer x = list.iterator().next();

http://nice.sourceforge.net/

» Posted by: Isaac Gouy at August 12, 2004 02:25 PM

"refactoring is just a string match"

This is just pure nonsense, as anyone with just the patience to do a Google search and read a little about the Refactoring Browser, for example (Johnson, et al) would see.

» Posted by: at August 12, 2004 09:18 PM

static typing has one advantage that i truly appreciate: it finds errors at compile time that otherwise would happen at runtime, where they would be much more dangerous.

saying things like "i don't make mistakes" sounds like famous last words to me.

i really don't see the advantage of dynamic typing: to me it seems like a powerful tool that is almost never useful in practice but can lead to dangerous mistakes at the same time. like multiple inheritance in c++.

i.e.: i have never seen a real world example where i would have wanted dynamic typing. it would save me a cast here and there, but, to be honest, that costs me about 1 second each time, especially using Eclipse and auto-fix.

» Posted by: nikster at August 13, 2004 03:36 AM

nikster: I agree, I don't see the benefits yet either. It's more like "trust us, you'll see when you get there". I haven't seen a good example yet.

» Posted by: Ryan at August 13, 2004 06:39 AM
Google
 
Search scope: Web ryanlowe.ca