It is often said that the developer should understand one level deeper than she’s working. If she’s writing Java, she should know how the JVM works. If he’s using a container, he should know conceptually what’s going on inside the container.
This statement is true for more than just runtimes and frameworks, but all the abstractions and innovations we’re building on. If we understand why our language provides certain features, then we can know when to use those features.
For instance, if we know the purposes of static typing, then we can know when to use it to the varying degrees available.
These purposes include:
1) Guarantee certain errors will not happen.
2) Document what each value means.
3) Identify places where a change impacts the code.
4) Prevent other coders from accidentally misusing our abstractions.
Based on the importance of the above purposes to our project — how bad is a runtime error? how many other developers need to use this? how confusing is it? — we might choose to use different levels of static typing.
In Java, for instance, we might shove information into a List of Strings all day long. Or, we might create custom datatypes to express an ordered collection of property names vs a nonempty collection of error messages. The more specifically we express our types, the more checking the compiler can do.
Yesterday I was bit in the butt when I instantiated an (unmodifiable) empty map and later tried to add to it. Why is there only one interface for Map? Why do I get UnsupportedOperationException instead of a red underline in my IDE saying “method ‘put’ does not exist on interface ReadableMap” ? When I have a read-only Map, I would like its type to express that.
Obtaining maximum benefit from the type system generally involves some attention on the part of the programmer, as well as a willingness to make good use of the facilities provided by the language.” — Pierce, Types and Programming Languages
The designers of Collections.emptyMap() did not put this level of attention into the type system.
Yet, in some cases typing this specificity is overkill. Pierce again:
The tension between conservativity and expressiveness is a fundamental fact of life in the design of type systems.
This means that static type checking sometimes prevents you from doing things that are perfectly valid. Maybe it’s just fine to pass in a constant string as a FirstName, but if your method expects a FirstName instead of String, extra code is required by Java’s type system.
Yesterday I took advantage of erasure to return a raw type when I didn’t need the data governed by the type parameter. Dirty, I know – but the statically checked type was overly restrictive in that case.
Understanding the purposes of static type checking can help us know what level of effort we should go to be as expressive as possible.
This is one example of getting past how to use the type system, into the why of its existence, so that we know when to use it. Understand one level of abstraction below where we work.
After the typical (for my era) start in BASIC, I began programming at the lower levels with Forth and assembly and worked my way up. I think, in a programming sense, this was one of the best things that ever happened to me. (It wasn't through any merit of my own, it was just a reflection of the kind of cheap hardware I had available.) There's something to be said for the old idea of forcing an art student to grind pigments and stretch canvas.