More Git Techniques
May 15, 2008 at 05:28 PM
In the spirit of Graeme Mathieson's git techniques, here are a few of my favorites, with their svn equivalents for reference.
Restore a file to repository version svn: rm file; svn up file git: rm file; git checkout file See what commits would be pulled on an update svn: svn stat -u git: git fetch; git log HEAD..origin/master See what code changes would be pulled on an update svn: svn diff -rHEAD git: git fetch; git diff HEAD..origin/master Grab one commit without any of the commits around it: svn: svn diff -rN1:N2 > my.patch; scp my.patch other.server; ssh other.server "patch < my.patch" git: git fetch; git cherry-pick [commit-hash] Revert a commit svn: svn merge -rN2:N1; svn commit -m "reverted commit N2" git: git revert [commit-hash] Set some changes aside to work on something else svn: cd ..; mv myproj myproj_stash; svn co svn+ssh://server/myproj git: git stash Discard local changes svn: svn revert -R . git: git reset —hard HEAD Edit recent commits svn: echo "Oops." git: git rebase -i HEAD~5
Firefox REST Plugin
May 12, 2008 at 03:02 PM
While we wait for web broswers to become fully REST-capable, Poster is a handy Firefox plugin for sending any type of HTTP request, including all four verbs and different content types. I usually use rest-client at a rush shell for one-offs; but if you need your browser's cookies for a call that can't be authenticated with http basic auth, or you just want a dialog that shows all the options visually, Poster is quite handy.
Don't Build the Super Nifty Node System
May 08, 2008 at 03:26 PM
Programmers tend to overdesign. Rather than building a quick solution that works right now for the specific case, we want to build one that will solve all problems of that type, both now and for all time. Dreaming In Code shows a particularly egregious case of this: the developers spend years building a framework for the app, rather than the app itself. This type of story is quite common. So frequent is this pitfall that the agile methodology mantra for avoiding it if often referred to by its abbreviation: YAGNI (You Aren't Going To Need It).
On the opposite extreme, there's the quick-and-dirty hack. But this has its own problems: it's fragile and inflexible. (It's telling that Microsoft's first pass at an operating system was named QDOS, where the QD stood for Quick-and-Dirty.) Quick hacks don't lend themselves to being built upon. Contrived example: a method called sumtwoand_two that returns the constant four would not be nearly as useful as a method called sum that takes two integer arguments and returns their sum. Generalized solutions are important - hell, they're what software is about.
So where is the right balance? My parter Orion puts it well: "You want to build an architecture that will be somewhat flexible, but not infinitely so."
Here's an anti-example: at our first venture together, we needed some customer relationship management software which the whole company could access. (This was eight years ago, and there weren't any suitable web-based commercial or open source choices that we knew of.) We ended up writing something called the Super Nifty Node System. It didn't track companies, people, and leads, like you might expect. Instead it had a single object type - a Node - and the users could define node types and relationships between them. The idea was that we were building a system which was so flexible that we wouldn't need to do any programming when someone wanted to track something new. We thought we were solving both the original problem and many related problems, and would never need to touch the software again.
In practice, this worked out poorly. Our non-technical users didn't understand how to create new node types, and didn't really want to anyway. Things like getting the fields to go in a certain order was really difficult, leading to a lot of user frustration on entering addresses. Worse was that simple programmatic tasks we wanted to perform - like pulling out a list of email addresses for all our partner companies, or a list of all our customers who had been with us for a year or more - required SQL statements with fifteen complex joins that wrapped around the screen six times. In retrospect, we should have just made tables for companies, people, and leads - even though there would have been a fair bit of duplicated code, this being in the pre-Rails (and generally pre-framework) era.
Many people have asked us why we didn't build Heroku to support multiple frameworks and languages. Why not Heroku for Python/Django, my second favorite language/framework combo after Ruby/Rails? Why not for PHP and some of the excellent MVC frameworks that exist for it? Why limit ourselves, and our potential audience, by building to the more specific case?
Were I building Heroku earlier in my career, I might have designed it with this in mind. I might have created an abstract base class App, and from that inherited RailsApp. Or perhaps App hasone :framework and hasone :language, and then Framework and Language are abstract classes that can be inherited by Framework::Rails, Framework::Django, Language::Ruby, and Language::Python. The Django and Python classes would have sat empty for months or years as we put our energy into developing for Ruby and Rails.
And there's a cost to leaving that infrastructure laying around. Just because you're not actively developing on a particular part of the codebase doesn't mean its maintenance-free: you've got more abstractions to keep in your head, more training time for new members on the team, more specs to keep running. Even just the extra files hanging around in the code tree adds a tiny bit of overhead for your brain each time you do a directory listing or otherwise manage the code.
So don't start generalizing until you have a strong need for it. Generalizing too early is the death of many a project - almost as often as generalizing too late.
A Better Daemonize
May 07, 2008 at 02:16 AM
Mongrel, Thin, and every other web application server I've ever used all suffer from a similar deficiency: they daemonize too early. That is, they daemonize prior to trying to boot your app, which means any error - even a really obvious, immediate boot problem - will silently feed into the log as the process dies, without so much as a peep on the command line.
Try this:
$ mkdir nothing $ cd nothing $ thin start -d && echo success
(Substitute "mongrel_rails" for "thin" here if you want, the result is identical.)
Wait, what? There's not even anything there. Why does it return true? Early daemonization, that's why.
A better approach would be to boot the app, and once it's online and listening and ready to serve requests, then return.
Rocking the Mocking
May 02, 2008 at 02:12 PM
How do you write a spec for this method without touching the filesystem or the user's environment?
def authkey File.read("#{ENV['HOME']}/.ssh/id_rsa.pub") end
Just repeat this mantra to yourself: It's Ruby. Everything Is An Object Or A Method. Objects And Methods Are Always Mutable.
Got your answer yet? Here's mine:
it "reads the ssh rsa key from the user's home directory" do ENV.should_receive(:[]).with('HOME').and_return('/home/joe') File.should_receive(:read).with('/home/joe/.ssh/id_rsa.pub').and_return('the key') @client.authkey.should == 'the key' end