So one point of upstart jobs is to be simple to write.
There's a lot of shell script magic in init.d scripts that gets repeated over and over. Case statements, pidfile tracking, lsb comment lines. Its not very clear how to write a GOOD init.d script without having read one.
If you've already gone through the trouble of writing all of that, then you don't need an upstart job unless, as I mentioned in another comment, you depend on another upstart job/event.
But really, upstart does make things really simple. You shouldn't need a pre-start unless you need to set things up like tmpdirs, ulimits, or runtime arguments. You shouldn't need a post-stop unless you want to make sure you tidy up after a service (the service really should be cleaning up after itself on normal exit).
Often times a giant init.d script with many options boils down to a 10 - 15 line upstart job. The most complex init.d scripts can have most of their logic dumped into pre-start's. The key there is that its just a little snippet of code to setup the environment for the process, and not logic on handling start/stop/respawn/etc.
The hardest part, and the one that people get wrong the most often, is knowing when to start/stop their job.
start on runlevel  seems logical, but ignores the fact that the network is coming up in parallel at that point, as are local filesystem mounts. The key is to try and figure out exactly the minimum things you need (other services, filesystems, network, etc) to get running, and start when those are done. Most traditional network services should do
start on (local-filesystems and net-device-up IFACE!=lo).