Dynamicity and Throwing Money at Problems
Howard's talk about reducing the amount of XML in Tapestry, coupled with Jamie Orchard-Hayes comment on tapestry-dev recently about how he likes the XML because it can be dynamically reloaded reminded me of a comment a coworker once made when describing some technology, along the lines of "that is the solution you get from throwing money at the problem, it solves the problem, but there is nothing elegant about it." That describes my feelings towards most of the XML configuration (and expression languages) in Java, right now.
In a post to the LL1 mailing list, someone (I wish I could remember who) posited the best definition of dynamic languages I have yet seen. The gist of it was that in a dynamic language, everything that can be deferred to runtime is deferred to runtime. Java doesn't support much dynamicity under this definition. All of the "#{foo} should be externalized" is often really trying to say "#{foo} should be deferred to runtime." XML has become the preferred way of doing this when any significant amount of deferring is going on. I posit that this is because there are cheap (bundled) and good xml parsers in Java, so you don't have to write your own (see lex and yacc). This is reasonable, and fair, and a solid chunk of what XML is designed to do.
However (you knew it was coming!) a lot of what is externalized in Java probably shouldn't be. It is going against the grain of the language. Java is designed to be type-safe, syntax-safe, memory-safe, stack(bashing)-safe, etc. We are all trying to circumvent that. It is like advertising a benefit of XML over HTTP being that "it lets you do RPC across those pesky firewalls those IT folks love." You throw away security (ie, knowing it will run) for... what? Yes, something valuable, but hold on, I bet it isn't even close to as valuable as you think because of some other stuff.
You claim to throw it away for configurability (sometimes valid, ie changing port tomcat runs on, sometimes wholly invalid, like changing the nature of a relationship in o/r mapping). You throw it claim to throw it away to reduce code (much more succint to do declarative execution off of some configuration, which is also valid sometimes, and wholly not other times). Finally, it is done for dynamicity -- being able to make changes at runtime and reload them (except you very rarely ever can (servlets in most cases you can, o/r mapping in most cases you cannot, for example)).
Now, seriously, consider throwing it away. If it is a configuration option only developers will make, put it in code. Try it out. If it is a systems-admin style config option, leave it external. Seriously, put developer configuration (your o/r mapping counts here, seriously, no admin is going to much with your o/r mapping, trust me), your port number ain't. You reduce code to save time, but most of the annoying hard to debug crap come sout of... yep, the xml configuration crap, so by reducing code you made it take an order of magnitude longer to make work. Nice one. Finally, for dynamicity -- give it up. Compiling Java is fast, and unless you really A) support runtime reloading, and B) actually even make use of it, then you are shooting yourself in the foot. Consider Beanshell, Groovy, Jython, or Rhino if you really need the dynamicity -- they are designed for it, Java isn't.
Now, short end note on expression languages. That is non-xml dynamicity for you (except for the wholly bizarre and disturbingly useful jxpath, which is, er wholly bizarre and disturbingly useful) in Java. It also is almost totally borked up. Seriously, don't create yet another expression language, just use GPath expressions, it is a language, designed to be a language, not a crippled toy. The plethora of expression languages is the same thing as the xml -- attempting to find a way to make Java dynamic (which it ain't, and that ain't bad, it just is!).
Now, I said "don't do #{x}" (note, the "#{x}" is not an expression language, it is valid ruby, a very dynamic language) an awful lot, and the examples have been small. So let's sketch a big example, one where tons of arcane "dynamicity through XML" is used: o/r mapping!
Imagine this:
public Customer[] findCustomersByAgeOfParentHobbyAndName(Integer age,
String hobby,
String name) {
Customers customers = this.db.Customers;
Parents parents = customers.joins.Parents;
return this.db.select(customers)
.join(parents).filter(parents.Age.greaterThan(age))
.end()
.filter(customers.Name.equal(name))
.and(customers.Hobby.equal(hobby))
.end()
.execute();
}
Reems of that code is generated, and is makes use of lots of finals and enums (hence direct access to fields). It is however, very bloody safe code. If it compiles, the query is almost guaranteed to be valid. The current issue for real safety is the join and filter, where you could pass something incorrect, but I can see the shape of doing it via generics so that you still get compile safety (it would involve classes like CustomerParentJoin
and overloaded join(..)
methods for each valid Join
type). Yes, the compiler can do all this, and all the peer tables for the database (the join classes, the table classes, etc, can be code generated by examining the schema in JDBC). Each of the arguments passed to the filter(..)
is its own type (e.g. ParentAgeGreaterThanCriteria
for filter(parents.Age.greaterThan(age))
), etc. Consider making use of java's lack of dynamicity, it can do nice things for you!
Java has a strong type system (okay, static type system, not strong, or do I have them reversed?) why do we try to force dynamicity into it? I have an answer (hypothesis, really) to that: We think in terms of duck typing, not in strong types, so we make code that matches how we think.