For me test-first-developed-code is a big factor in what I consider to be good code, which is I think what you mean when you say "elegant code". Elegance is a funny sort of word that people struggle to define, and, not necessarily, a huge player in what good code is (for which there are some relatively precise definitions).

Ideally, I like to write everything with good tests first. However, this requires a small amount of up-front design, usually done in your head (the stuff you do in order to know what your first test is). Most of the time that small amount of up-front design is small enough to cope with but when you're working with someone else's API that you don't know, or you're doing something very experimental, small isn't small enough. Here
are some possible ways you might solve that problem:

1. Prototype.

Write something to try out a design. "Hammer out", by all means. At this stage you want to ascertain whether your design is a good idea or not and you don't want to waste any time on code quality in case it's a bad one. Ruthlessly dispense with all unnecessary complexities, prove the concepts and nothing more.

If this doesn't work or you think you can do better with a different design you can throw this away. Suggestion: Commit it in your version control and then delete the file; it's around in version control if you need it again.

Sometimes your prototype will work in which case you should absolutely put in retrograde tests (the tests that you would have written if you were writing it test-first). Then, as others have suggested, refactor and refine.

2. Model

I still occasionally get out the ol' pen and paper and scribble some a loose UML class diagram showing the concepts I've identified and how they might link together. In my experience it's not worth spending much more than 30 minutes doing this and I never expect the design to work without alteration. Sometimes it deviates substantially when I start coding but it helped me get started so that's fine.

Other times you may find writing use cases or user stories more appropriate and there are number ways to do them.

This short blog post discusses the differences between types of modelling that I found very enlightening when I read: http://www.benjaminbooth.com/tableorbooth/2008/05/summary-di...

In the end it's whatever works for you to get a better idea of the problem. Mind mapping even! (Disclaimer: Never tried one that for code)

3. Pseudo-code

I rejected pseudo-code as one of those out of date techniques that has no place in the post-mainframe generation. But actually in select situations it's a nice compromise between a user story and a prototype. Typically the time you'll want to write pseudo-code is when you're working with something requiring a large number of procedural-like operations to be done to it in order to get a result. Stuff like data mungeing or gluing together a large number of systems or languages to achieve something.

I had a situation where I had to parse a file full of inconsistencies. Psuedo-code was excellent for that because it helped me identify the strategies I needed to deal with those inconsistencies. It ended up being a big-design-up-fronty sort of project but that was necessary because whenever I just dived into code I'd end up writing things that would work for one bit and then fall over on others; although a couple of those prototype components ended up being useful later on.

Possible Anti-pattern: The snowball prototype

If you are prototyping look out for the tendency to add to your prototype with increasingly more ambitious functionality. Once you prove one thing works it's very tempting to add the next idea you have to see if that works and so on. At some point you're going to have to stop and refactor otherwise you'll be programming spaghetti by the next day.

Alternatively create separate prototypes for each idea, refactoring out common components from the others where you need them. This should provide some reasonable designs (even without tests) whilst simultaneously maintaining a great deal of that speed that's so desirable in this experimental phase of development.

Hope this helps.