When Andy first asked me to write on "Beautiful Code" just two lines of Perl immediately sprang to mind.
use LWP::Simple;
my $page = get("http://www.google.com");
I see beauty in this code at many layers. Let's start with the first, the beauty of its simple interface.
Whether or not you know Perl, or even know how to program, you can figure out what that code does and that is beautiful. Why is that code so readily obvious to anyone who's used a web browser? Because the Gulf of Execution is so narrow.
The Gulf of Execution is the difference between the user's goal, stated in the user's terms, and the actions needed to achieve that goal. Here the user has a simple goal, "get a web page". A simple goal should be accomplished by simple actions related to that goal. With a web browser the user simply types in the URL and clicks a button. There's no reason why code should be any different.
This code accomplishes the user's goal with just two actions:
- Load LWP::Simple.
- Call get() with the desired URL.
One line of scaffolding, code which is just setup for the real action, brings in everything necessary to accomplish what the user wants in a neat package. One line of code gets them their web page as simple text, ready to be munged however the user likes. The final action is nearly a reiteration of the goal itself.
Compare with how you're supposed to do this long-hand:
use LWP::UserAgent;
my $ua = LWP::UserAgent->new;
$ua->agent("MyAgent/0.01");
my $req = HTTP::Request->new(GET => 'http://www.google.com');
$req->header('Accept' => 'text/html');
# send request
my $res = $ua->request($req);
my $page = $res->decoded_content;
List out the actions and we see just how wide a gulf this code has:
- Load LWP::UserAgent (which also loads HTTP::Request)
- Make a new LWP::UserAgent object.
- Set your HTTP user agent identification.
- Create a new HTTP::Request object.
- Prepare an HTTP GET request for the desired URL.
- Inform the server that we prefer HTML.
- Send the request on it's way to the server.
- Get the response back from the server.
- Decode the response according to the Content-Encoding.
And finally you have your page. Only the fifth action bares resemblance to the goal of "get a web page". The actions require intimate knowledge both of how the HTTP protocol works and how to work three different classes (one of which, HTTP::Response, is hidden). If the user doesn't understand all that, then it's all just so much mumbo-jumbo magic incantations which must be memorized by rote. It's the computing equivalent of setting the VCR clock.
At this point some of you might be thinking "that's not fair! There's so much more you can do with HTTP than just getting web pages! What about error handling? Authentication? Cookies? Multi-language support? Proxies?" You're right, LWP::Simple can't do anywhere near what LWP::UserAgent can do. But LWP::Simple can do one thing UserAgent can't: get a web page in just two steps.
There's an old cliche, "if it was hard to write, it should be hard to use", reflecting the programmer's own relationship with their creation. Of course the programmer thinks it's complicated, the act of getting a web page is a complicated process. They're focused on implementing the actions so that's what they tend to think about. The complexity becomes something to be proud of -- and why not they put a lot of work into it -- and that complexity leaks out into the interface so the user can appreciate just how much effort they went through.
But as far as the user is concerned it's dead simple: Here's a URL, give me back some HTML. They have no idea what goes on between, it could be tiny invisible winged ponies carrying little scrolls through the air for all they know, and they don't need to know. A good interface reflects the simplicity of the goal, not the complexity of the implementation.
The sad part of all this is in hiding complexity we allow the user to remain ignorant of all the effort put into making it simple. This leads to the converse fallacy at the user end, "if it was easy to use, it must have been easy to write" leading to a devaluing of all the programmer's effort. Alas, this is a paradox of good design. But look on the bright side, if the only time you hear about your software is when it breaks, take it as a compliment on your seemless interface design.
The second layer down, looking at the value of complexity underpinning simplicity, will be next.


Comments (8)
Many languages would be benefit from simple to use libraries with intelligent defaults. I'm reminded of building a simple IRC bot several years ago. I implemented the bot in Java with the pIRC library and in Perl using POE. My previous efforts with Java had been less than satisfying due to the amount of code needed to accomplish the most trivial of tasks. To my surprise, the pIRC code was simpler to understand and use compared to POE. Normally Perl was my language of choice for almost any task, but I was struck by the clean and simple design of Java+pIRC. I continue to use Perl for most programming tasks, but I realized then that the design of the supporting libraries was as important as the language itself. If Java had been created with simpler to use libraries rather than making everything so incredibly explicit, I probably would have been more inclined to use Java. As it is, the library design of Perl and Python is much more appealing and useful to me. Perhaps the recent popularity of dynamic languages on the JVM and CLR point to the need for more simplicity while leveraging the the very clear advantages of the JVM and CLR.
Posted by Kelly Jones | January 4, 2008 2:10 AM
I think you're choosing an unfair example with the LWP::UserAgent code.
my $ua = LWP::UserAgent->new;
my $res = $ua->get('http://www.google.com');
my $page = $res->content;
will work just fine.
Posted by Ambrose | January 4, 2008 3:45 AM
And, of course, this will also work
use LWP::UserAgent; my $page= LWP::UserAgent->new->get('http://google.com')->content;Posted by Donnie | January 4, 2008 10:34 AM
Wonderful post.
As a programmer, I find nothing more satisfying than creating a very simple interface for something that I know is very complex.
This is probably closely related to why I'm finding Ruby to be very appealing at the moment.
Posted by Matt Blodgett | January 4, 2008 11:34 AM
It's a one liner!
my $page = LWP::UserAgent->new->get('http://www.google.com')->content;
ps. check out this article how to download a youtube video with a similar perl one-liner: downloading youtube videos with a perl one liner.
Posted by Peteris Krumins | January 4, 2008 12:09 PM
Thank you for the feedback.
To those who pointed out the simpler ->get() interface I say, thank you. A sign of a great tutorial is when you can answer audience question and comments simply by proceeding to the next slide (or article in this case). :)
Posted by Schwern | January 7, 2008 2:00 AM
Good post. I'm a huge fan of ::Simple type modules that wrap oft-used functionality in an easy-to-use package. XML::Simple is probably my favourite in this area.
Posted by Dave Hodgkinson | January 7, 2008 7:00 AM
I stumbled across WWW::Mechanize about a year ago, which I now much prefer to LWP::UserAgent.
use WWW::Mechanize;
my $mech = WWW::Mechanize->new();
$mech->get( "http://search.cpan.org" );
print $mech->content;
While not quite as simple as LWP::Simple, the API is nice, easy and clean OO to use. It has groovy simple interface for grabbing links and images amongst doing other things.
# After calling $mech->get("http://somsite")
my @links = $mech->links();
# or
my @images = $mech->images();
A little more startup overhead, but has some really simple and well implemented interface.
Posted by Bergo | January 8, 2008 6:45 AM