<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;CkQDQ305eSp7ImA9WhVUFUU.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497</id><updated>2012-05-20T23:06:12.321-07:00</updated><title>You work for me, Computer</title><subtitle type="html" /><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://blog.brandonbloom.name/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>38</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/YouWorkForMeComputer" /><feedburner:info uri="youworkformecomputer" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;Ck8HQXg6eCp7ImA9WhVXFkw.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-8791396952737539021</id><published>2012-04-16T14:13:00.002-07:00</published><updated>2012-04-16T14:13:50.610-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-04-16T14:13:50.610-07:00</app:edited><title>Learn to Read the Source, Luke</title><content type="html">Apparently, I'm &lt;a href="http://www.codinghorror.com/blog/"&gt;Jeff Atwood&lt;/a&gt;'s guest blogger today: &lt;a href="http://www.codinghorror.com/blog/2012/04/learn-to-read-the-source-luke.html"&gt;Learn to Read the Source, Luke&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Source code access is near and dear to my heart, so head on over there and read it!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-8791396952737539021?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/5NfbMs06Cxg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/8791396952737539021/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=8791396952737539021" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/8791396952737539021?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/8791396952737539021?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/5NfbMs06Cxg/learn-to-read-source-luke.html" title="Learn to Read the Source, Luke" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2012/04/learn-to-read-source-luke.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUcFQH0zeyp7ImA9WhVTE0w.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-7773078865541083049</id><published>2012-02-26T19:23:00.001-08:00</published><updated>2012-02-26T19:23:31.383-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-26T19:23:31.383-08:00</app:edited><title>REST: One Thousand Inconsequential Decisions</title><content type="html">&lt;br /&gt;
&lt;div&gt;
Hardly a week goes by without yet another Hacker News front page post by a blogger making a bold declaration about some important thing you &lt;i&gt;absolutely must do&lt;/i&gt;&amp;nbsp;if you want your API to be&amp;nbsp;"RESTful". With &lt;a href="http://weblog.rubyonrails.org/2012/2/26/edge-rails-patch-is-the-new-primary-http-method-for-updates"&gt;Rails' recent PATCH announcement&lt;/a&gt;, REST is all over the front page again.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
In my experience designing and consuming APIs of varying levels of RESTfulness, I've concluded that most of the decisions involved in designing a RESTful API are completely inconsequential. PUT vs POST vs PATCH? HATEOAS vs manual URL construction? JSON vs custom Mimetype? It's all the same to an API consuming &lt;i&gt;developer&lt;/i&gt;. It might matter to some kind of generic crawler, but I'm not building an API for crawlers, so I don't care.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
What follows is a series of rants about various parts of the grand REST debate. This is by no means comprehensive. My goal with this post isn't to discourage you from writing a REST API. I simply want you to spend your time on the API decisions that matter, not the cosmetic, inconsequential ones.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;b&gt;&lt;span style="font-size: large;"&gt;HTTP Verbs &amp;amp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style="font-size: large;"&gt;&amp;nbsp;Status Codes&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The Rails PATCH announcement states "both PUT and PATCH are routed to update". So what's the difference? Apparently PATCH is for partial updates, where as PUT is for complete replacement of resources. Here's the funny bit: Both of these are implemented via POST with a &lt;i&gt;_method&lt;/i&gt; parameter for support of older browsers. The only two HTTP Verbs that actually matter are GET, for idempotent, cachable requests; and POST, for side-effects.&amp;nbsp;Even DELETE is insufficiently defined for any real application. What if you want to support both Archive and Delete like Gmail?&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Which response codes should you use? Those which have specific meaning to HTTP clients. Specifically: those treated differently by browsers. Should you use 201 on create instead of 200? Who cares? Should you use 301 or 302 for redirects? In an API, it doesn't matter. Pick one. Document it. For your actual web page, consult an SEO guide.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
What about errors? If you don't have permission to see a resource, is that a 403? Or do you hide the existence of the resource and return a 404 (like GitHub)? It doesn't matter. Return a 4xx for expected caller errors and a 5xx for unexpected errors. Pick one. Document it.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The smart thing to do is to always return your resources within one level of nesting. That is an &lt;i&gt;item&lt;/i&gt;&amp;nbsp;key in JSON or an &lt;i&gt;item&amp;nbsp;&lt;/i&gt;element in XML. Do this so that you can add an &lt;i&gt;error&lt;/i&gt;&amp;nbsp;key or other metadata without breaking changes. For example, you may want a &lt;i&gt;timings&lt;/i&gt; key for perf debugging. For lists, use an &lt;i&gt;items&lt;/i&gt;&amp;nbsp;key because pretty soon, you're going to want a&amp;nbsp;&lt;i&gt;paging&lt;/i&gt;&amp;nbsp;key. Also, for errors, make sure you include a non-localized (and hence, Google-able) error string; a status code is insufficient.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;b&gt;&lt;span style="font-size: large;"&gt;Hating On HATEOAS&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
HATEOAS, an absurd mouthful of an acronym, is a complete waste of time outside of read-only semi-structured datasets. If you're FreeBase, then go to town with HATEOAS conventions that are useful to you. However, for your typical webapp API, you simply cannot build a useful API client application without deep knowledge of the problem domain and the available API calls. You're going to have API documentation and you're going to have to read it.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
By all means, feel free to use HTTP 201 and a &lt;i&gt;Location&lt;/i&gt; header to return the location of a newly created resource. Or you could just return the resource as JSON with an &lt;i&gt;href&lt;/i&gt;&amp;nbsp;or &lt;i&gt;id&lt;/i&gt;&amp;nbsp;key. That's yet another inconsequential decision. Might as well go with the path of least resistance: return interesting data in the body of the response (ie. in the JSON) where it can be accessed without additional effort to parse the headers. As long as your response code is in the 200 range, no one is going to look at it further.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
I've seen people advocate &lt;i&gt;Link&lt;/i&gt;&amp;nbsp;headers such as &lt;i&gt;rel="newcomment".&lt;/i&gt;&amp;nbsp;It's weird to me because&amp;nbsp;you still need to know which &lt;i&gt;rel&lt;/i&gt; to lookup. The same thing applies to returning a bunch of extra URLs as metadata in your JSON. You still need to know which key to look up for the post-new-comment URL. You might as well just appended &lt;i&gt;"/comments"&lt;/i&gt; and avoid the indirection. &lt;i&gt;Link&lt;/i&gt; and &lt;i&gt;rel&lt;/i&gt; are useful to generic tools that want to treat a wide range of resources the same. There aren't enough common aspects between the domain models of a typical API to justify the spare brain cycles. Again, if you're Wikipedia, your use case is different. You have generic data that you want to traverse generically. And again, that's a mostly read-only use case.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style="font-size: large;"&gt;&lt;b&gt;Versioning&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Some REST fans advocate the use of custom MIME types for API versioning. These folks believe that version numbers in URLs are ugly. The usual objection is that resources should be&amp;nbsp;equitable&amp;nbsp;by URL and that &lt;i&gt;/v1/users/5&lt;/i&gt;&amp;nbsp;and &lt;i&gt;/v2/users/5&lt;/i&gt;&amp;nbsp;are different resources. The problem here is that this resource is actually identified by just the number. You don't need the whole URL and practical constraints are going to make it impossible to equate resources by URL.&amp;nbsp;Consider the constraint of browsers' Same Origin Access Policy. You need&amp;nbsp;&lt;i&gt;api.example.com&lt;/i&gt;&amp;nbsp;to run your public API on it's own IP address. And you need&amp;nbsp;&lt;i&gt;example.com/api/&lt;/i&gt;&amp;nbsp;to make calls from your web frontend without jumping through crazy IFrame interop hacks.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
There are two types of API changes: 1) Backward Compatible, and 2) Breaking Changes. In the case of backwards compatible changes, you don't have to, nor should you, change version numbers. Simply add the additional method or additional return data. Nothing to worry about. In the case of breaking changes, you &lt;i&gt;want&lt;/i&gt;&amp;nbsp;to break clients as loudly as possible. If you simply change the MIME type, most consumers of the API are totally going to ignore it. If you deploy your new API at &lt;i&gt;api2.example.com&lt;/i&gt;, then there is no risk of breaking older clients and they have to make a&amp;nbsp;conscious&amp;nbsp;decision to upgrade.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
In general, you're probably going to want to protect yourself with API keys. API keys provide the best way to version your API: record the desired API version with the generated key. Just stick an &lt;i&gt;api_version&lt;/i&gt; column on your &lt;i&gt;api_keys&lt;/i&gt; table. Expose that column as a drop down box in your API key management UI. This solution let's you leave the "ugly" version number out of the URL, but still enforce breakage on breaking changes. The best part is that you can totally ignore API keys and versioning when you start: Simply default unregistered API consumers to use v1 when you do implement v2.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
Last word on custom MIME types: One of the great promises of REST is the ability to treat generic things, generically. If your MIME type isn't a standard JSON MIME type, then how can you expect your browser to render it nicely with syntax highlighting and code folding? Your data is structured as JSON or XML; if you want to version the schema, use the facilities provided by XSDs or JSON Schemas, etc. But you're better off with documentation written by humans, for humans, than you are with formal schema.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;span style="font-size: large;"&gt;&lt;b&gt;REST: The Good Parts&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
There are a lot of good reasons why REST has gained popularity. For one thing, it isn't SOAP.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The good ideas in REST are primarily predictable URLs, contractual idempotence, the &lt;a href="http://news.ycombinator.com/item?id=2858712"&gt;preference of nouns over verbs&lt;/a&gt;, and CURL as a client library.&amp;nbsp;In practice, you can get contractual idempotence via GET vs POST. Predictable URLs are no different than predictable function names in all typical library code.&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
All this other stuff, the finer points of URIs and HTTP headers, are completely inconsequential to a developer programming against a well documented API.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-7773078865541083049?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/7Vew8CMIVAM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/7773078865541083049/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=7773078865541083049" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/7773078865541083049?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/7773078865541083049?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/7Vew8CMIVAM/rest-one-thousand-inconsequential.html" title="REST: One Thousand Inconsequential Decisions" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>3</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2012/02/rest-one-thousand-inconsequential.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkAGQXYzfip7ImA9WhRVFUQ.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-1918498034653403238</id><published>2012-01-14T18:22:00.000-08:00</published><updated>2012-01-14T18:32:00.886-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-14T18:32:00.886-08:00</app:edited><title>Plugging a hole in Microsoft's hiring pipe: IE Frame</title><content type="html">&lt;div&gt;
You're probably familiar with &lt;a href="http://code.google.com/chrome/chromeframe/"&gt;Google Chrome Frame&lt;/a&gt;, Google's clever workaround to combat Internet Explorer dependency in enterprises. You're probably also aware that &lt;a href="http://arstechnica.com/microsoft/news/2009/09/microsoft-google-chrome-frame-makes-ie-less-secure.ars"&gt;Microsoft is not a fan&lt;/a&gt;.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Microsoft desperately needs to implement an equivalent "Internet Explorer 10 Frame" mass deployed as a high-priority Windows Update. Failure to do so all but guarantees that Microsoft will become completely incapable of hiring a sufficient supply of talented graduates.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;
The key issue is that two of the major vectors for aspiring engineers are no longer dominated by Microsoft technology.&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;b&gt;Javascript is the New Basic&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
Microsoft QBasic was my first experience with programming, but I got started a few years earlier than most of my peers. The students I graduated college with largely had their first foray into text editing via HTML. Browsers are installed on every computer on the market and web development skills are vital for nearly all professional developers (even those not doing full-time web development). This puts Microsoft in a really bad spot: every budding developer's first impression of Microsoft is "IE SUCKS!!!!11!!one!".&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;b&gt;Microsoft's Stranglehold on Gaming is Loosening&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
When I was employed by Microsoft, I took an informal survey of my recently hired peers. I worked in the gaming division, so my view was skewed: they almost all got into programming via PC gaming. If you loved games, you ran Windows. If you wanted to make games, you learned Microsoft Visual C++. However, times are changing. Apple's iPhone is the biggest gaming platform on the planet. Your family computer is probably a Mac by now and your Xbox doesn't come with a compiler. Why not learn Objective-C? Furthermore, canvas games are popping up everywhere and it won't be long before WebGL is common place. Soon enough, a significant subset of aspiring game developers only have to boot Windows to test something in Internet Explorer. &lt;i&gt;sigh&lt;/i&gt;.&lt;/div&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-1918498034653403238?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/nIK4xH98lX8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/1918498034653403238/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=1918498034653403238" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1918498034653403238?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1918498034653403238?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/nIK4xH98lX8/plugging-hole-in-microsofts-hiring-pipe.html" title="Plugging a hole in Microsoft's hiring pipe: IE Frame" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2012/01/plugging-hole-in-microsofts-hiring-pipe.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0MEQH4-eyp7ImA9WhdVGUw.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-7936160275747534343</id><published>2011-09-24T17:40:00.000-07:00</published><updated>2011-09-24T17:43:21.053-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-09-24T17:43:21.053-07:00</app:edited><title>One year after accidentally falling in love with Rails</title><content type="html">About a year ago, I wrote a post titled&amp;nbsp;&lt;a href="http://blog.brandonbloom.name/2010/09/how-two-pythonistas-accidentally-fell.html"&gt;How two Pythonistas accidentally fell in love with Rails&lt;/a&gt;. Some people predicted that the honeymoon would be over quickly and that I would soon hate it.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;So, do I still love Rails?&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Yes. Absolutely.&lt;br /&gt;
&lt;br /&gt;
The prototype and first beta version of &lt;a href="http://www.thinkfuse.com/"&gt;my startup&lt;/a&gt; was built entirely on Rails. The framework enabled us to build our site significantly faster than we would have been able to without it. For that, I still love Rails. It was critical in helping us get to where we are today.&lt;br /&gt;
&lt;br /&gt;
It's the perfect &lt;i&gt;hammer&lt;/i&gt; for the v1, nebulous direction, rapid development,&amp;nbsp;&lt;i&gt;nail&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;That said, we're not using Rails for any new components.&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
After getting a lot of feedback from our first beta, we realized we had the wrong approach to the UI.&amp;nbsp;Now, in our second attempt, we've got a much richer client-side front-end, written in &lt;a href="http://jashkenas.github.com/coffee-script/"&gt;CoffeeScript&lt;/a&gt; and bootstrapped by &lt;a href="http://nodejs.org/"&gt;Node.js&lt;/a&gt;. Ruby/Rails is simply not the right tool for that job. In the new world of the browser app, even the folks of 37signals have been accused of&amp;nbsp;&lt;a href="https://github.com/rails/rails/commit/9f09aeb8273177fc2d09ebdafcc76ee8eb56fe33"&gt;propping&amp;nbsp;up their successor&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Luckily, we had a pretty good handle on the models in the problem domain. So I ran `git rm -r app/views` and effectively turned the Rails app into a Json API backend. The models have evolved, but not by much. In fact, there were a few months where the Ruby code remained practically unchanged while we developed the frontend.&lt;br /&gt;
&lt;br /&gt;
As time goes on, we've been breaking our API layer apart into several services. It makes development, deployment, and versioning easier. Service Oriented Architecture is working great for us... now that we know what services we need. These services are each too simple to justify the overhead of Rails. Some are Node.js, some are Sinatra, some are cron jobs and shell scripts. We're polyglots, so we're careful to choose the right tools for each job.&lt;br /&gt;
&lt;br /&gt;
We'd have gotten it very wrong if we tried to build services from the start. The monolithic Rails app was the correct evolutionary intermediate form.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Given a time machine, would I have done anything differently?&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Only one thing:&amp;nbsp;&lt;a href="http://seldo.com/weblog/2011/08/11/orm_is_an_antipattern"&gt;I wouldn't have used ActiveRecord&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
I guess&amp;nbsp;&lt;a href="http://blog.brandonbloom.name/2009/10/orms-and-declarative-schemas.html"&gt;I should have seen that one coming&lt;/a&gt;. ORMs are an insidious, broken abstraction. At first, they save you time and make you feel productive. They work out splendidly for a while, but like a virus, they infect every part of your application. One day, you hit that abstraction brick wall and it's too late to do anything about it without significant effort.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;b&gt;Are there things I hate about Rails?&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Ignoring ActiveRecord, only one thing really stands out: Performance.&lt;br /&gt;
&lt;br /&gt;
I'm not even talking about page load times. I'm talking about development time.&lt;br /&gt;
&lt;br /&gt;
Even with our modestly sized application, the impact on productivity is&amp;nbsp;atrocious. Our test suite runs far too slowly to be waited on for every checkin. Rake's startup time is too great to for its usage to be considered "interactive". Simple shell scripts have replaced practically all of our custom tasks; they are instantaneous.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Isn't this basically the same &lt;a href="http://blog.brandonbloom.name/2009/08/dropping-django.html"&gt;story I told about Django&lt;/a&gt;?&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Yeah, I guess it is.&lt;br /&gt;
&lt;br /&gt;
The story basically goes:&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Use a framework&lt;/li&gt;
&lt;li&gt;Be productive&lt;/li&gt;
&lt;li&gt;Encounter shortcomings&lt;/li&gt;
&lt;li&gt;Stretch it to work&lt;/li&gt;
&lt;li&gt;Watch it break&lt;/li&gt;
&lt;li&gt;Replace components&lt;/li&gt;
&lt;li&gt;Loop steps three to six&lt;/li&gt;
&lt;li&gt;Be productive&lt;/li&gt;
&lt;li&gt;"Where's the framework?"&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
I still stand by my original&amp;nbsp;criticisms&amp;nbsp;of Django (although, I'm sure much as changed in the last year). I'm also re-affirmed in my belief that big frameworks are generally bad. It just so happens that Rails is good enough and matches my mindset well enough to justify the framework pains.&lt;/div&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-7936160275747534343?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/KoANb8ZhVZU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/7936160275747534343/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=7936160275747534343" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/7936160275747534343?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/7936160275747534343?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/KoANb8ZhVZU/one-year-after-accidentally-falling-in.html" title="One year after accidentally falling in love with Rails" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2011/09/one-year-after-accidentally-falling-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkUARnw6fCp7ImA9WhdQFUs.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-8568685433749424217</id><published>2011-08-16T23:57:00.000-07:00</published><updated>2011-08-16T23:57:27.214-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-08-16T23:57:27.214-07:00</app:edited><title>Stupid brain trick: Tests and vertical splits</title><content type="html">Recently discovered a stupid brain trick that I thought was worth sharing.&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
When writing code with unit tests, I find it helpful to view the tests and the implementations side by side. I use vertical splits in Vim to do this.&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The stupid brain trick is this: &lt;b&gt;Put your tests in the left-hand split!&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Why? I traditionally used the left-hand split for the "main" thing I'm working on and the right-hand split for reference. I've watched many other developers do the same thing. The right split is constantly sub-split horizontally and always changing to various different files.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
In trying to be better about writing tests &lt;i&gt;first&lt;/i&gt;, I created a test code file before the implementation file, so it just happened to be in my left-hand split. After an hour or so of development, I realized that the "main" file placement of the tests meant that I focused more on them. I viewed the actual implementation file as just some ancillary file I had to tweak to get my tests to pass. It changed the focus of what I was doing from "Write a method that does X" to "Describe X". Describing something involves breaking it up in to smaller and smaller pieces until the implementations are so trivial that they basically write themselves.&lt;/div&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-8568685433749424217?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/giUucWSgX10" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/8568685433749424217/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=8568685433749424217" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/8568685433749424217?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/8568685433749424217?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/giUucWSgX10/stupid-brain-trick-tests-and-vertical.html" title="Stupid brain trick: Tests and vertical splits" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2011/08/stupid-brain-trick-tests-and-vertical.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck8FRH49eyp7ImA9WhZSE0g.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-6054908612527484999</id><published>2011-03-28T15:08:00.000-07:00</published><updated>2011-03-28T15:20:15.063-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-03-28T15:20:15.063-07:00</app:edited><title>Thinkfuse is hiring!</title><content type="html">If you caught the recent &lt;a href="http://techcrunch.com/2011/03/24/seattle-startup-thinkfuse-scores-500k-by-hanging-around-during-y-combinator-demo-day/"&gt;TechCrunch coverage&lt;/a&gt;, then you know that things are heating at my startup, &lt;a href="http://www.thinkfuse.com"&gt;Thinkfuse&lt;/a&gt; :-)&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We've got &lt;i&gt;lots&lt;/i&gt; to do, so we're looking for a kick-ass engineer who can get up and running quickly. My co-founder, &lt;a href="http://stevekrenzel.com/"&gt;Steve&lt;/a&gt;, and I are full-stack developers who really &lt;i&gt;give a shit&lt;/i&gt;, so we're looking for a Seattle-based developer who can keep up, or better yet, force us to struggle to keep up. Rails experience is a plus. Good sense of humor and beer drinking skills, a bigger plus. We're offering a solid salary and meaningful equity.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you're interested, reach out to me at &lt;a href="mailto:brandon@thinkfuse.com"&gt;brandon@thinkfuse.com&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-6054908612527484999?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/GERxNvBF1xk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/6054908612527484999/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=6054908612527484999" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/6054908612527484999?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/6054908612527484999?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/GERxNvBF1xk/thinkfuse-is-hiring.html" title="Thinkfuse is hiring!" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2011/03/thinkfuse-is-hiring.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D08GQ38ycCp7ImA9WhZXF0g.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-3826999096261613374</id><published>2010-09-17T11:31:00.000-07:00</published><updated>2011-05-07T01:10:22.198-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-05-07T01:10:22.198-07:00</app:edited><title>Where Rails Rocks (and where Python doesn't)</title><content type="html">&lt;div style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; background-color: transparent; font-family: Times; font-size: medium; "&gt;&lt;span id="internal-source-marker_0.20409649168141186" style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Here’s a list of things I like about Ruby on Rails. Almost every single one of these are available in at least one Python framework in some form, but the best thing about Rails is how polished the pieces are and how well they all fit together. The whole is truly greater than the sum of its parts.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Fantastic OOBE&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Rails has a fantastic out-of-box experience. Simply execute &lt;/span&gt;&lt;span style="font-size: 11pt; font-family: 'Courier New'; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;rails new&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; and you’re off to the races with a directory full of goodies that every real web app needs: dependency management, build system, multi-environment configuration, development storage, logging, custom error pages, robots.txt file and more. See section 3.2 of the &lt;/span&gt;&lt;a href="http://guides.rubyonrails.org/getting_started.html"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;getting started guide&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; for a complete list. Hell, they even fill out a .gitignore file for you! A place for everything and everything in its place.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Dependency Management: RubyGems&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;In the Python world, easy_install and its ilk are all kinds of broken. You can’t even uninstall a package! Luckily, there is &lt;/span&gt;&lt;a href="http://pypi.python.org/pypi/pip"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Pip and Virtualenv&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;, but assuming Pip supports all your packages, you’ve still got to make sense of dependency freezing and manually setup a virtual environment. With Rails, that all just works: &lt;/span&gt;&lt;span style="font-size: 11pt; font-family: 'Courier New'; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;rails server&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; runs in a virtualized environment and loads gems as defined by your &lt;/span&gt;&lt;a href="http://docs.rubygems.org/read/book/1"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Gemfile&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;; &lt;/span&gt;&lt;span style="font-size: 11pt; font-family: 'Courier New'; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;rails console&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; for a repl. No need to muck with sys.path! Any seasoned developed has spent many an hour in dependency hell, but it is still hard to justify the infrastructure investment before writing a line of HTML. Bonus points for the “vendor” directory, in case you need to patch a third party package (or Rails itself) and want to keep it under version control.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Build System: Rake&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;I’ll admit it: I like &lt;/span&gt;&lt;a href="http://www.gnu.org/software/make/manual/make.html"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Makefiles&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;. A lot of people really hate them, but they are just so damn useful. Frequently, you need to transform some set of files into some other set of files, but only when they change. Makefiles help you do that reliably. They accomplish their feat explicitly by &lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: italic; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;not&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; being a programming language, but a dependency and build rule declaration language. I’ve seen a lot of attempts at build systems that aim to be idiomatic with respect to their host languages. Java has Ant and Maven. Python has Scons and others. Scala has Simple Build Tool. None of them are as good as plain old Makefiles. Then, I discovered &lt;/span&gt;&lt;a href="http://rake.rubyforge.org/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Rake&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;. Rakefiles rock. They are 90% Makefiles, and 90% Ruby. Rake expertly uses the features of Ruby to craft an API/DSL that looks a lot like Make declarations without compromising on general purpose programmability.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Markup and Style Sheets: Haml and Sass&lt;/span&gt;&lt;br /&gt;&lt;a href="http://haml-lang.com/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Haml&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; and &lt;/span&gt;&lt;a href="http://sass-lang.com/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Sass&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; are so amazing, they ought to be illegal. Just click those links and check out the HTML and CSS comparisons. It is no contest. I never want to write HTML or CSS ever again. As far as I’m concerned, HTML and CSS are object code: to be generated by a compiler. Whenever I have to look at the generated code, it feels like I am reading assembly. Simply add &lt;/span&gt;&lt;span style="font-size: 11pt; font-family: 'Courier New'; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;gem ‘haml’&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; to your Gemfile, then start writing .html.haml files instead of .html.erb and .sass files instead of .css. Use &lt;/span&gt;&lt;span style="font-size: 11pt; font-family: 'Courier New'; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;html2haml&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; and &lt;/span&gt;&lt;span style="font-size: 11pt; font-family: 'Courier New'; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;sass-convert&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; to upgrade your crusty old markup and style sheets instantly.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Object Relational Mapper: ActiveRecord&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;I’ve discussed my &lt;/span&gt;&lt;a href="http://blog.brandonbloom.name/2009/10/orms-and-declarative-schemas.html"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;preference for database schema reflection&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; at great length, so I won’t reiterate here. I will, however, say that &lt;/span&gt;&lt;a href="http://api.rubyonrails.org/classes/ActiveRecord/Base.html"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;ActiveRecord&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; makes simple things simple and complex things possible while embodying successful best practices in its various subsystems, like migrations. It doesn’t try to be everything to everyone, unlike &lt;/span&gt;&lt;a href="http://www.sqlalchemy.org/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;SqlAlchemy&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;. And it seems to be nicely onion layered for pealing back when necessary. For example, &lt;/span&gt;&lt;a href="http://github.com/rails/rails/tree/master/activemodel"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;ActiveModel&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; validations can be used on plain old Ruby objects. Which brings me to...&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Form Handling: FormBuilder, params and mass assignment&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;I don’t need to learn and manage three different data marshaling techniques to map a database record to a web form. In fact, I don’t need to manage &lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: italic; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;any&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;. Schema reflection spares me in the model layer, &lt;/span&gt;&lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;FormBuilder/FormHelpers&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; in the views, and the hash &amp;amp; array structured params in the controllers layer. Almost every user interaction boils down to interpreting a form’s worth of JSON-like data structures as an API call, implemented via a (&lt;/span&gt;&lt;a href="http://github.com/ryanb/trusted-params"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;secured&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;) &lt;/span&gt;&lt;a href="http://railscasts.com/episodes/26-hackers-love-mass-assignment"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;mass assignment&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; to &lt;/span&gt;&lt;a href="http://railscasts.com/episodes/167-more-on-virtual-attributes"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;virtual attributes&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Templating: Layouts, Partials, and Helpers&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;When I’m getting my Haml on, I frequently want to run some non-trivial code. Feel free to shout about your separation of concerns religion, but I’ve got real things to worry about. Furthermore, we as engineers should be writing our production views/templates, not some hypothetical designer who is technical enough to write loops and branching statements, but not to be trusted with a function definition. Views are composed of markup and presentation declarations in the templates, presentation logic goes in helpers (which conveniently share scope with templates and controllers), and are neatly organized into &lt;/span&gt;&lt;a href="http://api.rubyonrails.org/classes/ActionView/Partials.html"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;partials&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;. Simple, but effective.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;URL Design: Routes&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Rail’s &lt;/span&gt;&lt;a href="http://edgeguides.rubyonrails.org/routing.html"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;routing&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; system makes clever use of blocks to provide a hierarchical, declarative URL map that just makes sense to read. I used to design URLs in an indented text file and then map it to a list of regexps, but the routes.rb file is close enough to a spec and doesn’t require me to even think about regexes, ‘nuff said.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Authentication: Authlogic&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Authentication is hard. Really, really hard. And boring. Really, really boring. But important. Really, really important. Everyone knows that the sign-up funnel is critical to get right and security is easy to get wrong. Most quality sites have a little bit of domain-specific special sauce in their authentication model. Trivial sign-up and log-in forms just don’t cut it. One size simply does not fit all for authentication front-ends, but the authentication back-ends tend to be pretty repeatable. &lt;/span&gt;&lt;a href="http://github.com/binarylogic/authlogic"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Authlogic&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; turns repeatable into re-usable, without the baggage of views you’re going to need to rewrite anyway. Thinking about authentication as CRUD operations on a user-session model blew my mind slightly. I had a full authentication system tuned to my needs up and running within two hours.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Authorization: CanCan&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;I hate &lt;/span&gt;&lt;a href="http://en.wikipedia.org/wiki/Access_control_list"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;ACLs&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;. &lt;/span&gt;&lt;a href="http://en.wikipedia.org/wiki/Role-based_access_control"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Role-based authorization&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; simply has too much impedance mismatch for most problem domains. &lt;/span&gt;&lt;a href="http://github.com/ryanb/cancan"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;CanCan&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; is a clever little library which makes zero assumptions about how permissions are represented. Simply declare a list of everything a user can do and how to tell if they may. Query with the &lt;/span&gt;&lt;span style="font-size: 11pt; font-family: 'Courier New'; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;can?&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; function, or assert with the &lt;/span&gt;&lt;span style="font-size: 11pt; font-family: 'Courier New'; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;authorize!&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; method. Beautiful.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Rope to Hang Yourself: Ruby Itself&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;One keyword in my previous post triggered much discussion: magic. The concept of magic is a topic for a whole ‘nother blog post (as I’ve already been hung a few times), but as several commenters pointed out: it’s all just Ruby. Between mixins, include, require, blocks, method_missing, symbols, and many other features, Ruby is a very capable internal domain specific language development toolkit. Most of the time, you don’t want a DSL. If you’re not an expert in both the host language and the DSL, there is almost certain confusion and maintenance danger. Most projects are either small or distinct enough to justify writing more verbose code that can more readily be understood by successors or even your future self. The gains of a DSL do not always outweigh the costs. Python excels at not supporting DSLs; it is basically executable pseudocode. However, when it comes to a task as common as web application development, it’s worth the time to develop or learn a DSL or two. Rails is loaded with DSL features and “magic” behavior that increases the learning curve, but also increases peak productivity.&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Community&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;These sites have been invaluable: &lt;/span&gt;&lt;a href="http://ruby-toolbox.com/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;The Ruby Toolbox&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;, &lt;/span&gt;&lt;a href="http://www.railsplugins.org/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;RailsPlugins&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;, &lt;/span&gt;&lt;a href="http://railscasts.com/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Railscasts&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;. The quantity and quality of &lt;/span&gt;&lt;a href="http://github.com/languages/Ruby"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Ruby libraries on Github&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; continues to impress. The educational infrastructure is extensive. Somehow, the Ruby community seems to consistently be at the forefront of the biggest trends in software development. Much like Rails compared to other frameworks, the community distinctions are subtle, but meaningful. For example, Rubyists championed Git while Python adopted Hg. To the untrained eye, they are virtually identical tools, but extensive experience with both has convinced me that Git is significantly superior. I’m new to the community, but it seems like Rubyists tend to insist on quality and bet on the winning horses.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Other Random Things&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;ul&gt;&lt;li style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; "&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Javascript and CSS minification are trivially easy with plug-ins&lt;/span&gt;&lt;/li&gt;&lt;li style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; "&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Static files are served with version stamps for better caching behavior&lt;/span&gt;&lt;/li&gt;&lt;li style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; "&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Console and file logging are usefully configured by default&lt;/span&gt;&lt;/li&gt;&lt;li style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; "&gt;&lt;a href="http://heroku.com/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;git push heroku&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;/li&gt;&lt;li style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; "&gt;&lt;a href="http://rails.vim.tpope.net/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;rails.vim&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;/li&gt;&lt;li style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; "&gt;&lt;a href="http://www.activemerchant.org/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;ActiveMerchant&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; looks awesome&lt;/span&gt;&lt;/li&gt;&lt;li style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; "&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Only been working with Rails for two weeks; lots more goodness to uncover&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Just who the hell do I think I am?&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;I’m nobody. Just some opinionated computer geek working on a start-up. Not unlike many of you! My co-founder and I are currently participating in TechStars Seattle and hope to launch something killer come demo day, November 11th, 2010. We’ve got a grand vision for next generation enterprise collaboration software, but we’re starting small by focusing on making &lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;span&gt;&lt;span&gt;&lt;a href="http://www.thinkfuse.com/"&gt;weekly status reports&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;a href="http://www.thinkfuse.com/"&gt;&lt;/a&gt;&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; less painful and more useful. More details soon, but please visit &lt;/span&gt;&lt;strike&gt;&lt;a href="http://www.thisweeklastweek.com/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;http://www.thisweeklastweek.com/&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; to register for a beta invite&lt;/span&gt;&lt;/strike&gt;&lt;a href="http://www.thinkfuse.com/"&gt;http://www.thinkfuse.com&lt;/a&gt;. Thanks!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-3826999096261613374?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/kPvbfiD8prE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/3826999096261613374/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=3826999096261613374" title="11 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/3826999096261613374?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/3826999096261613374?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/kPvbfiD8prE/where-rails-rocks-and-where-python.html" title="Where Rails Rocks (and where Python doesn't)" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>11</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2010/09/where-rails-rocks-and-where-python.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMBQH48fip7ImA9Wx5XE0o.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-2717690486322200085</id><published>2010-09-13T02:33:00.000-07:00</published><updated>2010-09-13T02:34:11.076-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-09-13T02:34:11.076-07:00</app:edited><title>How two Pythonistas accidentally fell in love with Rails</title><content type="html">&lt;div style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; background-color: transparent; font-family: Times; font-size: medium; "&gt;&lt;span id="internal-source-marker_0.5718986766878515" style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;One week ago, &lt;/span&gt;&lt;a href="http://stevekrenzel.com/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;my co-founder&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; and I found ourselves staring down a thick stack of paper UI prototypes; almost 50 pages total. One week before that, we threw out a perfectly good, working beta product because customer feedback suggested a significantly better path. The tale of how we ended up at that point is entertaining as well, but it will have to wait for another time. This is the tale of how our paper prototype turned a pair of long time &lt;/span&gt;&lt;a href="http://www.python.org/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Python&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; developers (and &lt;/span&gt;&lt;a href="http://blog.brandonbloom.name/2009/08/dropping-django.html"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;documented framework haters&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;) into &lt;/span&gt;&lt;a href="http://www.ruby-lang.org/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Ruby&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; on &lt;/span&gt;&lt;a href="http://rubyonrails.org/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Rails&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; developers.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Paper prototyping proved to be an extremely worthwhile exercise, but we really wanted something we could click around. We devised a strategy to build a clickable prototype that would evolve into production code. The plan was to stub out all of the views with static HTML and CSS. The CSS could be directly carried over to the product and the HTML could be retrofitted as templates. Having become religious about &lt;/span&gt;&lt;a href="http://sass-lang.com/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Sass&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; about a year ago, I fired up the file watcher (which automatically rebuilds CSS) and set to work filling a directory with sass and html files.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;After some time, I asked myself “I wonder if &lt;/span&gt;&lt;a href="http://haml-lang.com/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Haml&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; has a &lt;/span&gt;&lt;span style="font-size: 11pt; font-family: 'Courier New'; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;--watch&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; argument?” (it doesn’t) because Sass makes me hate Html’s verbosity. Armed with practically zero Ruby knowledge, I hobbled together a simple &lt;/span&gt;&lt;a href="http://rake.rubyforge.org/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Rakefile&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; and Haml watcher script and got back to work. A few Git pushes later, my co-founder, being the clever bastard that he is, simply installed Rails and the Haml plugin to replace my hacky scripts. “Neat,” I said and thought nothing of it.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Then we designed our URLs in an indented plain text file, which pretty much directly mapped into the &lt;/span&gt;&lt;a href="http://edgeguides.rubyonrails.org/routing.html"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;routes.rb&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; file and some trivial (empty method) controllers. Whoops! Now we’re writing Ruby.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;“At this point, we might as well try Rails proper.”&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Generated some models and corresponding migrations: “Wow, explicit schema with reflective model objects. None of this &lt;/span&gt;&lt;a href="http://blog.brandonbloom.name/2009/10/orms-and-declarative-schemas.html"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;declarative schema bullshit&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;.”&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Wired up some controllers. “The &lt;/span&gt;&lt;a href="http://rails.nuvvo.com/lesson/6371-action-controller-parameters"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;structured params&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; infrastructure is pretty cool.”&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;“Ruby is kinda fun. &lt;/span&gt;&lt;a href="http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Blocks&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; are super useful.”&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Integrated &lt;/span&gt;&lt;a href="http://github.com/binarylogic/authlogic"&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 153); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap; "&gt;Authlogic&lt;/span&gt;&lt;/a&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;. “Wow, that was easy. I really like how it does not force any front-end decisions on me.”&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;“Rails is full of evil black magic that I do not understand, but it seems to read my mind, so I don’t really care.”&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Started to apply some polish and work on the 10% that takes 90% of the time. “I think I can safely add Ruby to my resume now.”&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;Late at night, at the very end of the week, exhausted, we stared at each other. “Dude, I think we can show this off to people tomorrow.” Walking out the door of the office, my co-founder said to me “I feel so dumb for having ignored Rails for so long. I just assumed it was Ruby’s Django”.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;We &lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: italic; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;accidentally&lt;/span&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: bold; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt; implemented our entire beta product in one week, with zero prior Ruby or Rails experience.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: normal; font-style: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; "&gt;When I have some more time (and experience), I’ll try to write about specific design decisions that set Rails apart.&lt;/span&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-2717690486322200085?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/qiObWJwhCXE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/2717690486322200085/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=2717690486322200085" title="23 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/2717690486322200085?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/2717690486322200085?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/qiObWJwhCXE/how-two-pythonistas-accidentally-fell.html" title="How two Pythonistas accidentally fell in love with Rails" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>23</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2010/09/how-two-pythonistas-accidentally-fell.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0MMRns_cSp7ImA9WxFWF00.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-7667436166817352315</id><published>2010-06-04T19:37:00.001-07:00</published><updated>2010-06-04T19:38:07.549-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-04T19:38:07.549-07:00</app:edited><title>NoScript Add-on Install Base</title><content type="html">&lt;span &gt;The &lt;a href="http://noscript.net/" id="zj_s" title="official NoScript site"&gt;official NoScript site&lt;/a&gt; downloads via the &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/722/" id="kbbv" title="Mozilla Add-ons page"&gt;Mozilla Add-ons page&lt;/a&gt;, so its download count is probably accurate: 67,616,402&lt;/span&gt;&lt;br /&gt;&lt;p&gt;&lt;span &gt;NoScript does not publish their individual add-on usage statistics, but the global download/usage ratio can be calculated from the statistics on the &lt;a href="https://addons.mozilla.org/en-US/firefox/" id="dq5o" title="Firefox Add-on home page"&gt;Firefox Add-on home page&lt;/a&gt;:&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span &gt;1,962,617,946 add-ons downloaded&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span &gt;157,090,095 add-ons in use&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span &gt;About 8% of downloaded add-ons are still in use. Assuming NoScript's usage ratio is comparable to the average, approximately 5.4 million installations of FireFox are running NoScript. Let's ignore the fact that NoScript's usage ratio is probably much lower than the average, due to the fact that it &lt;i&gt;breaks most web pages&lt;/i&gt;.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span &gt;I'd wager that the average NoScript user has at least two machines, so the total number of NoScript users is probably less than 2.7 million.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span &gt;&lt;a href="http://www.google.com/publicdata?ds=wb-wdi&amp;amp;met=it_net_user&amp;amp;idim=country:USA&amp;amp;dl=en&amp;amp;hl=en&amp;amp;q=number+of+internet+users+in+america" id="seha" title="There are over 230 million internet users in the USA"&gt;There are over 230 million internet users in the USA&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span &gt;Even if 100% of NoScript users were Americans, they form 1% or less of the general population. If you, like me, believe that I have been generous to NoScript here, it is likely that no script users are no more numerous than 1 in 1,000.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span &gt;Even if the user is using NoScript, they can whitelist your site. You can probably add &amp;lt;noscript&amp;gt;WARNING: THIS SITE IS BUSTED WITHOUT JS&amp;lt;/noscript&amp;gt; to the top of your page and call it a day. If you are feeling generous, redirect no-script users to the mobile version of your site and tell them why.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span &gt;tldr: Assume that human user agents have Javascript.&lt;/span&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-7667436166817352315?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/ZEExgZW1PTA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/7667436166817352315/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=7667436166817352315" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/7667436166817352315?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/7667436166817352315?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/ZEExgZW1PTA/noscript-add-on-install-base.html" title="NoScript Add-on Install Base" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2010/06/noscript-add-on-install-base.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEUBQXYzcSp7ImA9WxFSEkU.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-3081866171636484955</id><published>2010-04-14T14:55:00.001-07:00</published><updated>2010-04-14T14:57:30.889-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-04-14T14:57:30.889-07:00</app:edited><title>Leaving Microsoft</title><content type="html">April 30th will be my last day on the Microsoft XNA team. I'm leaving to start work on my own startup full-time. I also hope to invest some more effort into this blog. Exciting times!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-3081866171636484955?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/Fc0L3CSn62Q" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/3081866171636484955/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=3081866171636484955" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/3081866171636484955?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/3081866171636484955?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/Fc0L3CSn62Q/leaving-microsoft.html" title="Leaving Microsoft" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2010/04/leaving-microsoft.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0EHRHw5fSp7ImA9WxNXGEg.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-1471324358664389393</id><published>2009-10-06T10:32:00.001-07:00</published><updated>2009-10-06T13:07:15.225-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-06T13:07:15.225-07:00</app:edited><title>ORMs and Declarative Schemas</title><content type="html">My &lt;a id="x_fb" href="http://blog.brandonbloom.name/2009/08/dropping-django.html" title="Dropping Django"&gt;prior post&lt;/a&gt; was more controversial than I anticipated. In hindsight, I should have realized what a hot button issue web frameworks are. One assertion went all but unnoticed. I expect it may be even more controversial: &lt;i&gt;the schema-generative ORM paradigm is fundamentally flawed&lt;/i&gt;.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Disclaimer&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;I came to this conclusion while working with Django's ORM, but this post is completely object-relational mapper agnostic. We are now using SqlAlchemy, but we are not using any of the many available declarative layers. Instead, we are using schema reflection and semi-automatically configured mappers. This is not an argument against ORMs. It is an argument against generating database schemas from ORM declarations. By extension, this is an argument against Django's ORM because Django uses an exclusively schema declarative model. That said, Django's ORM is far from alone in this camp.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;b&gt;Data Outlives Code&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;When code is dead and gone -- be it through rewrite, obsolescence, or by other means -- the data will still be there. Longevity implies slower evolution; data is always more difficult and riskier to change. Data is also more valuable. What if Facebook rebooted their database? They've already rebooted their software several times.&lt;br /&gt;&lt;br /&gt;Schemas are data. As data, schemas are longer lived, less flexible, and more valuable than code. These factors alone suggest that the database itself should hold the authoritative schema, not a class declaration in the code.&lt;br /&gt;&lt;br /&gt;If you have inherited data from another project, you already know this lesson. You can't generate the schema from code because the schema already exists. You can mimic the authoritative schema in your declarations, but it is easier and more accurate to use reflection.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Ineffective Domain Objects&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Object relational mapping is primarily a serialization problem. Every serialization solution has its quirks. The scale and number of quirks seem directly proportional to the absolute difference between the runtime and storage representations. Since a database is a completely distinct type system, rather than an opaque byte array, serialization to a database can have particularly quirky quirks.&lt;/div&gt;&lt;br /&gt;&lt;div&gt;Modeling is one of the fundamental challenges of software development. Capable developers prefer highly expressive or unconstrained type systems to aid with modeling. Generally, runtime type systems are more expressive than those found in databases. Declarative relational mappers, however, constrain the programming language type system to its less expressive counterpart. When building domain objects, the developer must think in terms of the database's type system, not the programming language's.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;While using a declarative object relational mapper, developers are effectively trying to design storage and runtime models simultaneously. On average, superior results are achieved by modeling these two concerns separately and then solving an additional subproblem: serialization mapping. You might wind up with slightly more code, but it will be easier to understand and maintain.&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;b&gt;SQL Is Not Going Away&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Despite forcing a less expressive type system on to the developer, declarative ORM layers attempt to treat the database as an implementation detail. However much we wish this were true, the database is not a detail which can be ignored. Sooner or later, you are going to have to open your database shell and write a SQL expression. This requires knowledge of your database's particular SQL dialect and idiosyncrasies. Exacerbating this issue, generated schemas are typically full of name mangling and other ugliness. It is far more pleasant to work with a carefully designed schema than one that compromises for the ORM or the runtime type system.&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;b&gt;Schema and Declarations Diverge&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Data migrations present the most pressing need to work with SQL directly. As an widely unsolved problem, automation can not be trusted. Unless you are painstakingly simulating the schema generator, the production database schema will slowly diverge. Most deviations are tolerable as they will not affect the runtime behavior, but it is wise to minimze differences between production, staging, and development environments. To faciliate this, store migration scripts and backed-up schemas in version control.&lt;br /&gt;&lt;br /&gt;Some deviations will directly affect runtime behavior. For example, consider the case where two versions of an application are running in production. An is_read boolean column was added to a message table in the database. It's default value is false. When a row's page is viewed, the is_read column is set to true. The old version of the application doesn't know about the column, so it can not set the flag. When the new version is rolled out to everyone, affected user will see a bunch of read items marked as unread! The solution is to set the column's default to true, but initialize it to false in the application. Declarations must either deliberately deviate from the schema or present a misleading default value to be overridden during initialization. This is just a simple example, real world schema migrations can be significantly more complex and suffer from numerous more subtle problems.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-1471324358664389393?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/b4Ii_cv65ks" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/1471324358664389393/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=1471324358664389393" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1471324358664389393?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1471324358664389393?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/b4Ii_cv65ks/orms-and-declarative-schemas.html" title="ORMs and Declarative Schemas" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>5</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2009/10/orms-and-declarative-schemas.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A04BQHg-eyp7ImA9WxNSEUo.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-7839271548232117293</id><published>2009-08-24T22:44:00.000-07:00</published><updated>2009-08-24T22:59:11.653-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-08-24T22:59:11.653-07:00</app:edited><title>URL routing and views</title><content type="html">As promised, I'd like to elaborate on the URL routing system I came up with.&lt;br /&gt;&lt;br /&gt;Weighing at less than 200 lines of code (including example), I'll let it speak for itself: &lt;a href="http://www.brandonbloom.name/static/blog_files/viewdemo.zip"&gt;download it&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This approach seems to be working great for us. Love it? Hate it? Feel free to let me know what you think.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-7839271548232117293?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/9sZsmoaEM-s" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/7839271548232117293/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=7839271548232117293" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/7839271548232117293?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/7839271548232117293?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/9sZsmoaEM-s/url-routing-and-views.html" title="URL routing and views" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>6</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2009/08/url-routing-and-views.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUIMQHo7fSp7ImA9WxNTF0w.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-7619874311475185718</id><published>2009-08-19T00:07:00.001-07:00</published><updated>2009-08-19T14:33:01.405-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-08-19T14:33:01.405-07:00</app:edited><title>Dropping Django</title><content type="html">&lt;p&gt;My partner and I built a non-trivial web site on Django. When the next version ships, there might not be a single Django module imported.&lt;/p &gt;&lt;p&gt;We're not trying to drop Django; it is just sort of happening. Piece by piece, it is failing to meet our needs. Despite the marketing copy on the Django site, most components of the framework are tightly coupled enough to make customization frustrating. It is often easier to rewrite core framework components than to implement them on top of the existing  extensibility points.&lt;/p &gt;&lt;p&gt;What follows is a loose chronology of our migration away from Django.&lt;/p &gt;&lt;p&gt;&lt;b&gt;URL Routing&lt;/b&gt;&lt;/p &gt;&lt;p&gt;A flat list of patterns violates the DRY principal when creating nested URLs. Trees are a superior representation. Having a tree of views also enabled us to optionally associate a "binder" function with each node. These bind functions are executed for each URL component from left to right, filling the template context as they go. Breadcrumbs are automatically generated as each node binds, but only the last node executes its full view logic.&lt;/p &gt;&lt;p&gt;&lt;b&gt;Authorization&lt;/b&gt;&lt;/p &gt;&lt;p&gt;Our site enforces permissions on every resource, but Django's database ACLs would have been prohibitively numerous. Instead, views or their URL binders may raise an AccessDenied exception. Upon catching such an exception, a middleware layer serves a login form. This ensures users have permission to access the current resource, as well as all ancestor resources bound to the URL.&lt;/p &gt;&lt;p&gt;&lt;b&gt;Authentication&lt;/p &gt;&lt;p&gt;&lt;/b&gt;Both of Django authentication's key extensibility points are flawed. These two extensibility points are "user profiles" (storing additional per-user data) and custom credentials (such as for logging in via email address instead of username). Django's documentation and numerous internet sources cover both topics, but all of the guidance lacks important caveats. The admin UI, in particular, is very easy to break with either extensibility mechanism.&lt;/p &gt;&lt;p&gt;Extending the User model with the ORM requires a one-to-one database relationship. This relationship can be implemented with a "user profile" setting, an explicit foreign key, or model inheritance. Each approach has its own strengths and weaknesses in terms of performance, API semantics, subtle behavioral changes, and outright bugs.&lt;/p &gt;&lt;p&gt;Enabling custom credentials requires implementing a trivial authorization "backend" object. Unfortunately, it is non-trivial to replace usernames with email addresses. The admin UI's login form refuses to accept email addresses without hacking the template. Even if you hacked the template, the User model would still enforce a non-null constraint on the username field and the generated database schema enforces a uniqueness constraint as well. It turns out to be easier to fill the username field with a dummy value and "support" both forms of authentication with your backend, but you won't come to that conclusion until your head has already bore a hole in your desk.&lt;/p &gt;&lt;p&gt;&lt;b&gt;Templating&lt;/p &gt;&lt;p&gt;&lt;/b&gt;We do our best to keep view and template logic separate. Django's templates are targeted at designers, who aren't implementing any real logic anyway. However, we're a pair of hackers. Sometimes it is just more convenient to put a little bit of logic in the views. Besides, templates are code; code needs to be reviewed and tested. We wouldn't ever hire a designer who couldn't pass a code review for some trivial template logic.&lt;/p &gt;&lt;p&gt;We needed a pragmatic template language to replace Django's idealistic one. Any template language with greater expressive power would have been welcome, but Jinja2 fit the requirements and provided the easiest migration path. Ultimately, we'd prefer to use something like HAML, but there doesn't seem to be a Python equivalent besides the inactive GHRML project. We are, however, using SASS. I will never write CSS by hand again.&lt;/p &gt;&lt;p&gt;&lt;b&gt;ORM and Admin UI&lt;/b&gt;&lt;/p &gt;&lt;p&gt;One of Django's most touted features is the Admin UI. For simple "active record" style database models, the Admin UI is a huge time saver. Sadly, it struggles a little bit with nullable fields and is tricky to customize. You'll definitely need to write custom UI for complex models, but by and large the admin solves the problem at hand: viewing, creating, updating, and deleting database rows.&lt;/p &gt;&lt;p&gt;After using the Admin for a little, I found myself missing Microsoft Access. I never thought I'd say that, but it is true. &lt;strike&gt;Django's admin does not support sorting, filtering, or other impromptu queries.&lt;/strike&gt; &lt;small&gt;&lt;i&gt;Edit: It turn's out I was mistaken about sorting and filtering, but I stand by the core message of this section.&lt;/i&gt;&lt;/small&gt; I found myself writing impromptu queries in the database and Python shells. After a while, I just gave up and installed a desktop client. I haven't visited the Admin UI since.&lt;/p &gt;&lt;p&gt;Django's ORM has shortcomings with respect to querying, especially for joins and aggregation. It has been improving over time, but it will likely never reach the capability of projects solely focused on databases, such as SqlAlchemy. With the admin having fallen into disuse, the Django ORM lost all advantage. Beyond Django's specific weaknesses, I've come to believe that the schema-generative ORMs paradigm is fundamentally flawed. That is a topic that deserves an entire (Django-agnostic) post of it's own. We are now using SqlAlchemy via schema reflection; no declarative layer.&lt;/p &gt;&lt;p&gt;&lt;b&gt;Form Validation and Generation&lt;/b&gt;&lt;/p &gt;&lt;p&gt;Here is where our chronology meets present day. We are still using Django form validation, but never used form generation beyond scaffolding. Nearly all of our templates customize labels and display of errors. Additionally, embedding widget information in the Python code is cumbersome during template development. Django forms is a quality validation library, but there are some inconsequential style things that I like better about FormEncode. Preferences aside, the difference isn't large enough to justify switching.&lt;/p &gt;&lt;p&gt;While I like FormEncode, I'm still not sold on its anti-form-generation companion, htmlfill. I think there is a middle ground with form generation that provides scaffolding during development, smoothly transitions to production use, and cooperates with validation. As we implement more complex client views, I'll be on the lookout for ways to improve our form development toolbox.&lt;/p &gt;&lt;p&gt;&lt;b&gt;So, ugh... What's left? &lt;/b&gt;&lt;/p&gt;&lt;p&gt;Besides a few isolated helper functions, not much is left of Django.&lt;/p &gt;&lt;p&gt;The last big ticket item is the HTTP framework and WSGI server. We could continue using Django as if it were CherryPy or Paste, but Django has this nasty habbit of insisting on running your code for you. The settings and manage.py infrastructure are fiddly for deployment and don't really add any value over simple scripts using our application like a library. Might as well use a simpler WSGI library, and replace those over-engineered management/commands/foo.py files with vanilla scripts/foo.py files.&lt;/p &gt;&lt;p&gt;&lt;b&gt;Moral of the Story&lt;/b&gt;&lt;/p &gt;&lt;p&gt;I'm sure there are numerous lessons to be generalized from this journey. Personally, I've developed a moderate fear of the word "framework", as well as altered the way I think about software abstractions. I think the most important lesson, however, is one I already knew: choose the right tool for the job. Unfortunately, we had no idea what the right tool was when we started. I'm not sure we know any better now.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-7619874311475185718?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/cCerFANbN90" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/7619874311475185718/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=7619874311475185718" title="40 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/7619874311475185718?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/7619874311475185718?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/cCerFANbN90/dropping-django.html" title="Dropping Django" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>40</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2009/08/dropping-django.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEYBR348fCp7ImA9WxJXGEs.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-4006940402542870204</id><published>2009-06-12T20:02:00.001-07:00</published><updated>2009-06-12T20:02:36.074-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-12T20:02:36.074-07:00</app:edited><title>AppWeek</title><content type="html">&lt;p&gt;&lt;a href="http://blogs.msdn.com/shawnhar/archive/2009/06/12/appweek.aspx"&gt;Shawn's AppWeek post&lt;/a&gt; inspired me to write one too. AppWeek is our chance to be creators for a little while and it was a lot of fun. I didn't set out to build something nearly as ambitious as &amp;quot;Super Avatar Sample Smashup EXTREME! - 'Capture the Cat' edition&amp;quot;, but I did get to take a swing at a game I've wanted to build for a while: &lt;del&gt;Rock'em Sock'em Avatars&lt;/del&gt; Avatar Boxing. Avatars, being a new feature in this release, were an unwritten requirement for all of the AppWeek games. Between SASSECTCE, my game, and the many others, Avatars were chasing cats, beating each other up, play futuristic sports, falling off buildings, dancing in a cloud of gems, being launched from canons to save the world, and much more. All this excitement was almost too much for a bunch of exhausted engineers, but that's what the beer was for during the game unveilings.&lt;/p&gt;  &lt;p&gt;Here's what the game looked like with the basic animations wired up. You'll notice that the avatars have been hitting the gym. That's because their arms were too short to reach each other! I added a little extra bulk because I was laughing too hard not to. I directly bound the game pad triggers to the shoulder and elbow joints and rigged up the chase camera sample to inspect my work. There wasn't much game play yet, but it was already fun. That's always a good sign.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/_JRoal87eB78/SjMWErtvNwI/AAAAAAAAASg/KNCPdBd4tyI/s1600-h/bdbx1-1%5B2%5D.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; border-top: 0px; border-right: 0px" border="0" alt="bdbx1-1" src="http://lh5.ggpht.com/_JRoal87eB78/SjMWGJ31oUI/AAAAAAAAASk/Rc7YMqpoXPk/bdbx1-1_thumb.png?imgmax=800" width="244" height="139" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh4.ggpht.com/_JRoal87eB78/SjMWTtx6BxI/AAAAAAAAASo/8wyqMFcIBQI/s1600-h/bdbx1-2%5B2%5D.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; border-top: 0px; border-right: 0px" border="0" alt="bdbx1-2" src="http://lh3.ggpht.com/_JRoal87eB78/SjMWW4Vkk8I/AAAAAAAAASs/ZKAP9xib_m8/bdbx1-2_thumb.png?imgmax=800" width="244" height="139" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Even with just one week, I decided to invest some time into debugging visualizations. That turned out to be a really great idea.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/_JRoal87eB78/SjMWYjC74LI/AAAAAAAAASw/Wor0Vg8KzqE/s1600-h/bdbx1-5%5B2%5D.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; border-top: 0px; border-right: 0px" border="0" alt="bdbx1-5" src="http://lh5.ggpht.com/_JRoal87eB78/SjMWZGEBb1I/AAAAAAAAAS0/etLyKATKNqA/bdbx1-5_thumb.png?imgmax=800" width="244" height="139" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Then, I added some collision spheres for the heads, hands, and upper bodies. This was a hacky, trial and error process. Thankfully, C# compiles quickly.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh4.ggpht.com/_JRoal87eB78/SjMWcFD8z8I/AAAAAAAAAS4/2ceLPhGwDQU/s1600-h/bdbx1-4%5B2%5D.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; border-top: 0px; border-right: 0px" border="0" alt="bdbx1-4" src="http://lh5.ggpht.com/_JRoal87eB78/SjMWdsfPpWI/AAAAAAAAAS8/yxdpSUsG3wA/bdbx1-4_thumb.png?imgmax=800" width="244" height="139" /&gt;&lt;/a&gt;&amp;#160; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/_JRoal87eB78/SjMWjHxpusI/AAAAAAAAATA/Exib_dAenPs/s1600-h/bdbx1-3%5B2%5D.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; border-top: 0px; border-right: 0px" border="0" alt="bdbx1-3" src="http://lh3.ggpht.com/_JRoal87eB78/SjMWk13YF9I/AAAAAAAAATE/qBXF2hUqIBo/bdbx1-3_thumb.png?imgmax=800" width="244" height="139" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;At this point, I spent an entire day working on the physics. I wanted the avatars to bounce/wobble when they got hit, so I rigged up some complex spring systems. Things were starting to work, but I'm generally pretty bad at this sort of thing and my simulation routinely exploded. The avatars arms went shooting off into space and I was getting pretty frustrated. No screen shots of that chaos because I am embarrassed.&lt;/p&gt;  &lt;p&gt;With half a day to go, I added the obligatory damage bars and some rudimentary hand-to-head collision detection.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/_JRoal87eB78/SjMWpC6hp8I/AAAAAAAAATI/w4NN2y3gHww/s1600-h/bdbx1-6%5B2%5D.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; border-top: 0px; border-right: 0px" border="0" alt="bdbx1-6" src="http://lh5.ggpht.com/_JRoal87eB78/SjMWqLAx2mI/AAAAAAAAATM/QRuBL6DiPfo/bdbx1-6_thumb.png?imgmax=800" width="244" height="139" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;I was feeling pretty good about the game, despite my physics failures, it was pretty fun anyway. I wondered down the hall to chat with Jace, who had just added sound effects to his game. His game was hilarious before, but the sound effects were &lt;em&gt;priceless&lt;/em&gt;. I ejected the sound effect CD out of his machine, yoinked it, and took off running. An hour later (and 10 minutes &lt;em&gt;after&lt;/em&gt; the deadline), my game had some sweet punch and miss sounds. I also made the avatars' heads pop up when their damage bar was full, accompanied by an awesome zip-tie sound.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh3.ggpht.com/_JRoal87eB78/SjMWxWjyCCI/AAAAAAAAATQ/RfSo9rXaqqk/s1600-h/bdbx1-7%5B2%5D.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; border-top: 0px; border-right: 0px" border="0" alt="bdbx1-7" src="http://lh4.ggpht.com/_JRoal87eB78/SjMWx7_lM2I/AAAAAAAAATU/P7RHzLPCFH0/bdbx1-7_thumb.png?imgmax=800" width="244" height="139" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;At our team happy hour, I'd like to think Avatar Boxing was a fan favorite. I certainly had fun making it! I hope everyone enjoys Avatar support in the new XNA Game Studio.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-4006940402542870204?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/G3M7uTX2ey0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/4006940402542870204/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=4006940402542870204" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/4006940402542870204?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/4006940402542870204?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/G3M7uTX2ey0/appweek.html" title="AppWeek" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh5.ggpht.com/_JRoal87eB78/SjMWGJ31oUI/AAAAAAAAASk/Rc7YMqpoXPk/s72-c/bdbx1-1_thumb.png?imgmax=800" height="72" width="72" /><thr:total>5</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2009/06/appweek.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck4GQXc7fCp7ImA9WxJTEk8.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-1269333104236652268</id><published>2009-04-20T02:33:00.001-07:00</published><updated>2009-04-20T02:48:40.904-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-20T02:48:40.904-07:00</app:edited><title>PowerShell: condemned to reinvent</title><content type="html">&lt;p&gt;I tried PowerShell when it was first released, but never used it for real work. I recently attended a &amp;quot;brown bag&amp;quot; presentation about PowerShell. This presentation spurred me to augment our team's environment with PowerShell and I have been using it every day since.&lt;/p&gt;  &lt;p&gt;In the past weeks using and abusing PowerShell, I have drawn two conclusions:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;PowerShell has a killer set of standard tools with brilliantly designed usability.&lt;/li&gt;    &lt;li&gt;The PowerShell team doesn't understand UNIX and therefore were condemned to reinvented it, poorly -- with apologies to Henry Spencer.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;First things first: if you spend any time working with Windows, get PowerShell. Now. Stop reading my blog and go &lt;a href="http://www.microsoft.com/windowsserver2003/technologies/management/powershell/download.mspx"&gt;download it&lt;/a&gt; immediately. It mops the floor with cmd.&lt;/p&gt;  &lt;p&gt;The key premise behind PowerShell is that it operates on live .NET objects. This is beneficial because it eliminates a lot of the text cutting and manipulation common in shell scripts. Additionally, it puts the full .NET Base Class Library into your scripting toolbox. PowerShell tools, known as commandlets, typically only render the most common fields for their objects, but the less common fields are easily available in memory. By convention, Commandlets are named with a &lt;em&gt;verb&lt;/em&gt;-&lt;em&gt;noun&lt;/em&gt; pattern and support a common command line parsing behavior. The repository of commandlets and the command line options of each are easily queried and highly consistent. All this meta-data makes PowerShell a breeze to learn.&lt;/p&gt;  &lt;p&gt;I fell in love with the the discoverablity and ease of use when I tried to kill a collection of runaway processes:&lt;/p&gt;  &lt;pre class="code"&gt;PS&amp;gt; get-command -noun process&lt;br /&gt;&lt;br /&gt;CommandType   Name           Definition&lt;br /&gt;-----------   ----           ----------&lt;br /&gt;Cmdlet        Get-Process    Get-Process [[-Name] &amp;lt;String[]&amp;gt;] [-Verbo...&lt;br /&gt;Cmdlet        Stop-Process   Stop-Process [-Id] &amp;lt;Int32[]&amp;gt; [-PassThru]...&lt;br /&gt;&lt;br /&gt;PS&amp;gt; get-process notepad | stop-process&lt;br /&gt;PS&amp;gt; get-alias | where { $_.definition.contains(&amp;quot;Process&amp;quot;) }&lt;br /&gt;&lt;br /&gt;CommandType   Name   Definition&lt;br /&gt;-----------   ----   ----------&lt;br /&gt;Alias         kill   Stop-Process&lt;br /&gt;Alias         ps     Get-Process&lt;br /&gt;&lt;br /&gt;PS&amp;gt; ps someotherapp | kill&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;OK, that's pretty cool and oh-so-very Unixy -- right? Wrong. Notice the &amp;quot;CommandType&amp;quot; column in the results of get-command. There are many other types of commands besides commandlets: functions, filters, scripts, applications, etc. Each of these has slightly different semantics for pipes and parameters. Applications, for example, have no way of accepting .NET object pipes. You must develop a separate commandlet. Yikes!&lt;/p&gt;&lt;p&gt;Compare to Unix: all commands are applications which accept a command line and pipe byte streams in and out. Much simpler, but byte streams aren't as friendly, discoverable, and maintainable as object streams. However, the brilliantly simple thing about Unix is that, when you get right down to it, object streams are just byte streams! There is absolutely nothing stopping you from implementing &lt;em&gt;get-process&lt;/em&gt; and &lt;em&gt;stop-process&lt;/em&gt; as Unix programs which pipe object references, JSON, pickled Python objects, XML, S-expressions, or any other data format you fancy. Doug Mcllroy, the inventor of Unix pipes, was right: text streams are the universal interface.&lt;/p&gt;&lt;p&gt;Actually, this is no different on Windows. All of the PowerShell commandlets could have been implemented as applications which import a library. This library would replace &lt;em&gt;main&lt;/em&gt; in much the same way as &lt;em&gt;winmain&lt;/em&gt;, provide a metadata enriched implementation of &lt;a href="http://www.gnu.org/software/hello/manual/libc/Getopt.html"&gt;&lt;em&gt;getopt&lt;/em&gt;&lt;/a&gt;, &lt;em&gt;man&lt;/em&gt;, etc. There is no need to invent a new shell in order to acquire the power of piping objects. Sure, cmd is old and needed to be retired for many other reasons, but it is a real shame that the PowerShell toolset is not available to those of us stuck in batch scripts.&lt;/p&gt;&lt;p&gt;Personally, I would really like to see such a library developed. Microsoft has certainly proved one thing with PowerShell: steep learning curves are not intrinsic to command line interfaces. Unfortunately, commandlets are two steps forward and one step backwards. I have no doubt that we can retake that forward step.&lt;/p&gt;&lt;style type="text/css"&gt;&lt;br /&gt;.code, .code pre&lt;br /&gt;{&lt;br /&gt;	font-size: small;&lt;br /&gt;	color: black;&lt;br /&gt;	font-family: consolas, "Courier New", courier, monospace;&lt;br /&gt;	background-color: #ffffff;&lt;br /&gt;	/*white-space: pre;*/&lt;br /&gt;}&lt;br /&gt;.code pre { margin: 0em; }&lt;br /&gt;&lt;/style&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-1269333104236652268?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/wmMwZMyjFQo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/1269333104236652268/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=1269333104236652268" title="7 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1269333104236652268?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1269333104236652268?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/wmMwZMyjFQo/powershell-condemned-to-reinvent.html" title="PowerShell: condemned to reinvent" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>7</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2009/04/powershell-condemned-to-reinvent.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEIBRH04cCp7ImA9WxVXEUo.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-757509981108407907</id><published>2009-02-09T02:49:00.001-08:00</published><updated>2009-02-09T02:49:15.338-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-09T02:49:15.338-08:00</app:edited><title>Language-oriented programming: too much, too fast</title><content type="html">&lt;p&gt;There was some &lt;a href="http://news.ycombinator.com/item?id=452919"&gt;recent discussion on Hacker News&lt;/a&gt; about the &lt;a href="http://www.onboard.jetbrains.com/is1/articles/04/10/lop/index.html"&gt;2004 article on &amp;quot;Language Oriented Programming&amp;quot;&lt;/a&gt; by &lt;a href="http://www.sergeydmitriev.com/"&gt;Sergey Dmitriev&lt;/a&gt;. With my growing interest in programming languages, this article, and the pending release of &lt;a href="http://www.jetbrains.com/mps/index.html"&gt;JetBrain's Meta Programming System&lt;/a&gt;, I have been thinking a lot about the future of programming.&lt;/p&gt;  &lt;p&gt;As Sergey points out, MPS is not the first entry into this paradigm of software development. &lt;a href="http://en.wikipedia.org/wiki/Intentional_programming"&gt;Intentional Programming&lt;/a&gt; was being &lt;a href="http://www.youtube.com/watch?v=tSnnfUj1XCQ"&gt;demonstrated by Microsoft Research&lt;/a&gt; as early as 2000. Wikipedia lists several implementations of the &lt;a href="http://en.wikipedia.org/wiki/Language-oriented_programming"&gt;language-oriented programming&lt;/a&gt; concept. (OK, that is enough links for now!) Sadly, none of these systems have been met with wide spread success. Despite my unlimited respect for the JetBrains team and love of their products -- especially Resharper -- I expect MPS to fail to achieve critical mass. I don't think many programmers would disagree. It's just too much, too fast.&lt;/p&gt;  &lt;p&gt;Software development needs to evolve, not start anew. If you change too much at once, the common developer simply won't accept it. Language designers are still re-inventing Lisp piece by piece in an effort to make sense of all the different concepts for &lt;a href="http://www.nikhilk.net/Personas.aspx"&gt;Mort&lt;/a&gt;. In order to improve adoption of language-oriented programming concepts, the critical path must be identified and the programming community must be lead down that path one step at a time.&lt;/p&gt;  &lt;p&gt;I believe that the first stepping stone is the box editor.&lt;/p&gt;  &lt;p&gt;Both MPS and Intentional Programming provide a visual tree of boxes editor and store the abstract syntax tree in a sort of source code database. Both of these concepts are critical to power of language-oriented programming, but changing the storage of source code is simply too radical of a change. Well, actually, source code is routinely stored in databases for IDE features, but the authoritative storage always remains text files. All of a programmers most trusted tools operate at the string level and it is simply impractical to throw everything out at once.&lt;/p&gt;  &lt;p&gt;This is why I propose the creation of a new editor which uses an abstract syntax tree box editing model, but preserves the source as text. This editor would have to play nice with other developers who are using traditional text editors. It should be possible for a single developer to try the editor for a day or two without another collaborator even noticing. There are clear benefits to a box editor even in the absence of language-oriented features. For example, navigation of source code by parent, child, or sibling relationships instead of by word or by line. Advanced renders can be used to layout math expressions or to show a referenced image file in a comment.&lt;/p&gt;  &lt;p&gt;Editors are a religion, start a new one. If some box editor becomes popular enough, other tools would spring up around them. To help the process, the editor should have well exposed extension and composition mechanisms. For example, it should be trivially easy to add new renderers for various nodes. It should be equally trivial to utilize the abstract syntax tree system to create stand alone code analysis tools.&lt;/p&gt;  &lt;p&gt;If box editors garner enough of a following, then the language-oriented programming advocates can seek the next stepping stone.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-757509981108407907?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/E8zHi7TkgK4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/757509981108407907/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=757509981108407907" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/757509981108407907?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/757509981108407907?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/E8zHi7TkgK4/language-oriented-programming-too-much.html" title="Language-oriented programming: too much, too fast" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>4</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2009/02/language-oriented-programming-too-much.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0UAQHs7fCp7ImA9WxVQF04.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-1954132929655789531</id><published>2009-02-03T23:07:00.001-08:00</published><updated>2009-02-03T23:07:21.504-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-03T23:07:21.504-08:00</app:edited><title>Break the cycle of broken builds</title><content type="html">&lt;p&gt;Broken builds suck. Breaking the build sucks. No one wants to have their work stopped by others' build breaks and no one wants to cause work stoppage on account of their own build breaks.&lt;/p&gt;  &lt;p&gt;So then why do builds break so often? You'd think that after so many years of software development, someone would have solved this problem. Microsoft employees tens of thousands of engineers across hundreds, if not thousands, of code projects. Many of those code projects are development tools for the engineering community at large as well as engineers within Microsoft. Yet, still, all of the tools at the disposal of those engineers are highly prone to build breaks.&lt;/p&gt;  &lt;p&gt;Below, are three ideas for reducing build breaks. Together, they could all but eliminate the problem.&lt;/p&gt;  &lt;h4&gt;Use a better version control system&lt;/h4&gt;  &lt;p&gt;Everyone keeps raving about distributed version control, and for good reason. I won't rehash the argument here, but I will say: branch early and merge often. &lt;a href="http://weblog.masukomi.org/2007/8/31/dear-perforce-fuck-you"&gt;Perforce (better known as Source Depot within Microsoft) and it's spiritual successor Team Foundation Server are completely unbearable&lt;/a&gt;. A great majority of build breaks can be contained if people are only pulling known good changes from dedicated integrators. Everyone checking in all at once to a main branch simply doesn't work with anything but the most tightly knit of teams. Even if the does build break, it should only block the person waiting on that particular change.&lt;/p&gt;  &lt;h4&gt;Isolate build breaks&lt;/h4&gt;  &lt;p&gt;`sd submit -c 12345` translates roughly to &amp;quot;I'm 100% confident that my changes are solid and am equally confident that I am submitting the changes that I think I am.&amp;quot; You never can be 100% confident, don't take any chances: submit to a private branch which is being monitored by a build service. The server should detect your submission, run a full buddy build (with unit tests!), and then submit it to the shared branch only after it has passed.&lt;/p&gt;  &lt;p&gt;If your builds take too long for this, people should be able to grab changes down from your unverified branch on an as-needed basis. If this happens too frequently: your builds take too long. Refactor your system into smaller components and formalize the contractual interface between those components. Then buddy build just the components which have changed. Verify that the interface hasn't changed with a unit test. This will stop compile errors at the boundaries.&lt;/p&gt;  &lt;h4&gt;Go lazy, be late&lt;/h4&gt;  &lt;p&gt;This one is a big of a pipe dream... software systems developed with dynamic languages do not experience build breaks nearly as often as those developed with static languages. This is because dynamic languages are typically lazy-loaded and utilize late bounded method invocations. If someone adds a new button to the UI which calls a method in a file they forgot to add to source control, that should only block people who want to click the button!&lt;/p&gt;  &lt;p&gt;There is no firm technical reason why statically or JIT compiled systems can't simulate this. Most compile errors could theoretically be treated as a runtime error. Clearly, compile errors are valuable, but should be ignorable whenever possible. Don't ignore them in your known good builds, but ignore them when they stand between you and forward progress.&lt;/p&gt;  &lt;p&gt;Since most of us aren't writing our own compilers are full tool chains, here is some more practical advice for projects using statically compiled languages: write code which fails gracefully. Runtime errors can be build breaks just the same as compile errors, but try to defer the negative consequences as much as possible. One broken feature shouldn't break the whole project; unless, of course, you are about to ship. In production builds, perform verification code at startup or in unit tests, but try to be lazy and late in development builds.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-1954132929655789531?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/3dJhDfRtdOY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/1954132929655789531/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=1954132929655789531" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1954132929655789531?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1954132929655789531?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/3dJhDfRtdOY/break-cycle-of-broken-builds.html" title="Break the cycle of broken builds" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2009/02/break-cycle-of-broken-builds.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkINRn0zfCp7ImA9WxVRGUo.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-548682939177538385</id><published>2009-01-26T03:49:00.001-08:00</published><updated>2009-01-26T03:49:57.384-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-26T03:49:57.384-08:00</app:edited><title>Contributions</title><content type="html">&lt;p&gt;The modern, read/write web thrives on processes which accept and coordinate a large set of contributions from a set of contributors that is almost as large. Before this was common on the web, open source software capitalized on the aggregate efforts of communities. However, not every web 2.0 site or open source project succeeds in attracting or retaining contributors. In the case of web sites, this is often due to poor user interface or other highly visible problems&lt;/p&gt;  &lt;p&gt;Software projects, however, typically suffer from a different problem: the cost of creating and integrating contributions. This ailment also afflicts proprietary software, where it is actually rarely diagnosed, and even more rarely treated.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;I argue that all substantial software projects should strive to minimize two variables:&lt;/em&gt;&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;em&gt;Aggregate cost adding a contributor [1]&lt;/em&gt;&lt;/li&gt;    &lt;li&gt;&lt;em&gt;Maximum individual cost of accepting a contribution&lt;/em&gt;&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;If either of these variables are not minimized, a project will fail to fully utilize the efforts of its key members, never mind begin to capitalize on the long tail of contributions. High contribution costs waste countless millions of hours of productivity.&lt;/p&gt;  &lt;p&gt;In a company, the easiest way to measure the cost of adding a contributor is to measure the time from signing the employee agreement until the moment that the software builds and runs on the new-hire's development machine. Don't forget to count this in man-hours because this will surely require the efforts of a manager, a more senior engineer, or both.&lt;/p&gt;  &lt;p&gt;Additionally, you should also count the time before the new developer feels some level of confidence in the codebase. Can this new person dive right in and start hacking on a new feature? Could they fix a bug without everything falling apart? How many times will they need to knock on the door of the expert? [2]&lt;/p&gt;  &lt;p&gt;Measuring the cost of accepting a contribution is subtly different. The aggregate cost is not of critical importance as long as it is justifiable given the benefit of accepting a contribution. However, minimizing the aggregate cost of accepting a contribution is already the goal of any successful software project. Teams are always striving to be more efficient, but it is a local optimization. If you want to increase the number of contributors and contributions, you need to minimize the maximum cost to maintainers, newcomers, other team members, and communities.&lt;/p&gt;  &lt;p&gt;As any good manager knows, they must suffer the pain of meetings, accept frequent context switching, and act as a shield from politics. They must do all of this to ensure their reports are as productive as they possibly can be. Similarly, senior engineers should donate some of their time to analogous tasks in component and systems ownership. Although these tasks introduce a cost which causes an individual contributor to spend less time actually contributing, it opens the door to greater net contributions.&lt;/p&gt;  &lt;p&gt;So here come's the concrete advice part of the post...&lt;/p&gt;  &lt;h5&gt;Some ways to reduce the cost of adding a contributor&lt;/h5&gt;  &lt;ul&gt;   &lt;li&gt;Buddy builds - One button should instantly and automatically allocate a fresh machine, enlist in the source tree, build without additional dependencies, execute all tests, and provide detailed status and results.&lt;/li&gt;    &lt;li&gt;Standardized tools - Allow contributors to transfer their skills in version control, issue tracking, building, testing, and communication. Every team is different, but the more you can reasonably share, the more you simulate one cohesive team.&lt;/li&gt;    &lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Distributed_revision_control"&gt;Encourage branching&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;Understand that contributions can come from anyone [3]&lt;/li&gt;    &lt;li&gt;Keep documentation up to date and complete - Hold new project members accountable for the correctness and completeness of the newcomers wiki page.&lt;/li&gt; &lt;/ul&gt;  &lt;h5&gt;Some ways to reduce the cost of accepting a contribution&lt;/h5&gt;  &lt;ul&gt;   &lt;li&gt;Automated unit tests - Insist on a high code coverage and a low flakiness rating. Require a 100% pass-rate before committing to the main branch.&lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.python.org/dev/peps/pep-0008/"&gt;Explicit coding standards&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;Code reviews&lt;/li&gt;    &lt;li&gt;&amp;quot;Lieutenants&amp;quot; - Put an &amp;quot;owners&amp;quot; file in each directory of your source tree. Fill it with the usernames of those who should perform the code reviews for those files and sub-directories. Keep these files up to date.&lt;/li&gt;    &lt;li&gt;&lt;a href="http://code.djangoproject.com/wiki/VersionOneRoadmap"&gt;Widely and clearly communicate project vision and direction&lt;/a&gt; - This ensures misguided contributions will be rejected swiftly.&lt;/li&gt;    &lt;li&gt;Regular release cycle - Do I really need to say &amp;quot;release early, release often&amp;quot;? Releasing often enough will force you to reduce the cost of your release process as well.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Got any more?&lt;/p&gt;  &lt;p&gt;&lt;font size="1"&gt;[1] This post was prompted by my &lt;strike&gt;startup&lt;/strike&gt; hobby project's failure in this respect. Adding a third team member took several hours of my attention, but should have taken several minutes.      &lt;br /&gt;[2] Sorry Stephen!      &lt;br /&gt;[3] Some discussion lists at Microsoft are notorious for responding to internal feedback with the URL &lt;/font&gt;&lt;a href="http://career/"&gt;&lt;font size="1"&gt;http://career/&lt;/font&gt;&lt;/a&gt;&lt;font size="1"&gt; and possibly a snarky comment. These people are artificially setting the cost of adding a contributor to be a career left-turn and the cost of accepting outside contributions to infinity. I hope these dinosaurs go extinct.&lt;/font&gt;&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-548682939177538385?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/lqNtuN55AhY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/548682939177538385/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=548682939177538385" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/548682939177538385?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/548682939177538385?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/lqNtuN55AhY/contributions.html" title="Contributions" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2009/01/contributions.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMDQH07eip7ImA9WxRUEkk.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-2574545841795690793</id><published>2008-11-20T20:07:00.001-08:00</published><updated>2008-11-20T20:07:51.302-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-20T20:07:51.302-08:00</app:edited><title>Rift is available on NXE!</title><content type="html">&lt;p&gt;You can download the demo or purchase the game &lt;a href="http://marketplace.xbox.com/en-US/games/media/66acd000-77fe-1000-9115-d80258550117/?gu=66acd000-77fe-1000-9115-d80258550117&amp;amp;sb=1&amp;amp;mt=32&amp;amp;p=1&amp;amp;of=0"&gt;on the web&lt;/a&gt; or from the Community Games section of the Games Marketplace on your NXE dashboard. If you get it on the web, it will automatically be downloaded to your Xbox 360 the next time you sign in.&lt;/p&gt;  &lt;p&gt;Sure, the game isn't worth the $1.75, but you can still pay for it anyway because you are super generous. And sure, the game has serious design flaws because it was put together in three days as a homework assignment. And sure, the game is loaded with performance issues because it was ported to XNA Framework 3.0 and &amp;quot;polished&amp;quot; for release in two evenings involving beer. But you are super generous.&lt;/p&gt;  &lt;p&gt;Definitely check it and all the other great XNA Community Games launch titles. They all have free demos. Exciting times!&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-2574545841795690793?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/h_K1gCXfp4M" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/2574545841795690793/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=2574545841795690793" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/2574545841795690793?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/2574545841795690793?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/h_K1gCXfp4M/rift-is-available-on-nxe.html" title="Rift is available on NXE!" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/11/rift-is-available-on-nxe.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0QFQnkyfSp7ImA9WxRVEUQ.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-7572299314055831695</id><published>2008-11-08T17:48:00.001-08:00</published><updated>2008-11-08T17:48:33.795-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-08T17:48:33.795-08:00</app:edited><title>Rift submitted for play test</title><content type="html">&lt;p&gt;I've uploaded &lt;a href="http://blog.brandonbloom.name/2008/05/rift.html"&gt;Rift&lt;/a&gt; for play test by XNA Creators.&lt;/p&gt;  &lt;p&gt;If you are an XNA Creators Club premium member, please download the game and post your comments in &lt;a href="http://forums.xna.com/forums/p/20020/104752.aspx"&gt;this forum thread&lt;/a&gt;. Later this week, I will be submitting the game for final peer review. Hopefully, the game will make it through the pipes in time for the New Xbox Experience and Xbox LIVE Community Games launch on November 19th!&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-7572299314055831695?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/96uF90DGDBg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/7572299314055831695/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=7572299314055831695" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/7572299314055831695?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/7572299314055831695?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/96uF90DGDBg/rift-submitted-for-play-test.html" title="Rift submitted for play test" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/11/rift-submitted-for-play-test.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkQARn4-fCp7ImA9WxRWEUg.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-5464491425222193964</id><published>2008-10-27T18:45:00.001-07:00</published><updated>2008-10-27T18:45:47.054-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-10-27T18:45:47.054-07:00</app:edited><title>Control flow has to flow</title><content type="html">&lt;p&gt;I've been experiencing MSBuild a bit at work lately. Ignoring the mortal sin of designing a programming language within XML, MSBuild fails on usability in another fundamental way: control flow has to flow.&lt;/p&gt;  &lt;p&gt;In general, there are two key approaches to control flow. One approach is the procedural &amp;quot;jump&amp;quot; approach. This includes all manner of conditionals, loops, and more. The second approach involves functional constructs such as recursion and pattern matching. These two approaches are blended between procedural languages, functional languages, and everything in between and beyond.&lt;/p&gt;  &lt;p&gt;The procedural and functional mechanisms can be visualized in terms of forwards and backwards. Procedural control flow moves forward in a set of steps which are executed in sequence as defined. Functional control flow begins with the results and works its way backwards pulling in bits and pieces of data and logic along the way.&lt;/p&gt;  &lt;p&gt;MSBuild uses neither of these approaches.&lt;/p&gt;  &lt;p&gt;That was a lie. MSBuild targets are executed in sequence procedurally. Targets can also define a &amp;quot;DependsOn&amp;quot; property which works functionally to pull in pre-requisite tasks. &lt;/p&gt;  &lt;p&gt;The truth is that MSBuild introduces (and often requires) a very unwelcome third approach in the form of the &amp;quot;Condition&amp;quot; property. The &amp;quot;Condition&amp;quot; property is neither forwards nor backwards control flow. It is sideways. MSBuild has only one global namespace of variables (confusingly, called &amp;quot;properties&amp;quot;) which can be set or tested at any time.&lt;/p&gt;  &lt;p&gt;With either a procedural or functional control flow, you can follow the flow simply in your text editor. In a structured procedure, you simply trace through the code pretending to be a human interpreter. In a functional method or when dealing with sub-procedures, you can trivially search for functions by name as they are called. In a sense, you are &amp;quot;flowing&amp;quot; through the code like water though pipes. You are either pumping water through or sucking water out.&lt;/p&gt;  &lt;p&gt;With sideways control flow, you need to search for all references to every variable at every step of both the procedural and functional code flows. This is a laborious and error prone operation because after finding a relevant &amp;quot;Condition&amp;quot;, you need to analyze the forward and backwards control flow which led to that sideways control flow construct! There is no &amp;quot;flowing&amp;quot;.&lt;/p&gt;  &lt;p&gt;Sideways control flow leads to a combinatorial explosion of forward and backwards control flows. The developer is required to keep all of these flows in their heads all at once. Meanwhile, in normal programming languages, you only ever need to deal with just two: forwards and backwards.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-5464491425222193964?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/srJubbZCNw8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/5464491425222193964/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=5464491425222193964" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/5464491425222193964?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/5464491425222193964?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/srJubbZCNw8/control-flow-has-to-flow.html" title="Control flow has to flow" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/10/control-flow-has-to-flow.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DU8MSHs5fip7ImA9WxVSE0w.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-7237354119845005885</id><published>2008-10-18T13:42:00.001-07:00</published><updated>2009-01-07T00:44:49.526-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-07T00:44:49.526-08:00</app:edited><title>Blog resolution</title><content type="html">&lt;p&gt;I now have 9 blog subscribers. Maybe more; if you aren't subscribed to the &lt;a href="http://feeds.feedburner.com/YouWorkForMeComputer"&gt;http://feeds.feedburner.com/YouWorkForMeComputer&lt;/a&gt; version, please switch. However, my site gets a pretty strong amount of other traffic from recruiters and other people more interested in my resume than my blog. While feed subscribers never even see the site in its original layout, about 65% of the visitors have a horizontal resolution greater than or equal to 800 pixels. The previous template was designed for a minimum horizontal resolution of 600 pixels, so I added another 100 pixels to the content.&lt;/p&gt;  &lt;p&gt;I've been writing my posts with &lt;a href="http://get.live.com/writer/overview"&gt;Windows Live Writer&lt;/a&gt;. It has a really cool feature:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;"Weblog" menu &lt;/li&gt;    &lt;li&gt;"Edit Weblog Settings..." menu item&lt;/li&gt;    &lt;li&gt;"Editing" tab&lt;/li&gt;    &lt;li&gt;"Update Style" button&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Poof! Now my editor has the same width as my Blogger template. I love it when software just works. Now if only it could do this automatically...&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-7237354119845005885?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/GG32_wHPt8k" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/7237354119845005885/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=7237354119845005885" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/7237354119845005885?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/7237354119845005885?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/GG32_wHPt8k/blog-resolution.html" title="Blog resolution" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/10/blog-resolution.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0AMQXc5fyp7ImA9WxRQFEg.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-6177480679461732414</id><published>2008-10-08T01:48:00.001-07:00</published><updated>2008-10-08T01:49:40.927-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-10-08T01:49:40.927-07:00</app:edited><title>Imaginary APIs</title><content type="html">&lt;p&gt;As promised, I will explain my favorite way to design APIs. I call it &amp;quot;imaginary APIs&amp;quot;. I'm far from the first person to have thought of this and I've even seen this used in practice by others, but I have no idea if there is an official name for it.&lt;/p&gt;  &lt;p&gt;In short, the idea is that you write code which consumes an imaginary API and then you imagine it works. Then, you write some more code which does something more advanced with your imaginary API and imagine that works too. Then, you figure out how to get your magic code to compile, run, and work.&lt;/p&gt;  &lt;p&gt;Success is measured by how closely your final API matches your imaginary API and by how much you need to modify your magic code to be a working sample you can ship along side your API. That's it.&lt;/p&gt;  &lt;p&gt;As an example, let's imagine an API for a simple home movie editing program. I'm sorry about the excessively narrow blog formatting.&lt;/p&gt;  &lt;p&gt;&lt;font face="Courier New"&gt;var video = new Movie();     &lt;br /&gt;video.StoryBoard.Add(      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Image.FromFile(&amp;quot;title.png&amp;quot;));      &lt;br /&gt;video.StoryBoard.Add(      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; new CrossFade(TimeSpan.FromSeconds(2.0f)));      &lt;br /&gt;video.StoryBoard.Add(      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Movie.FromFile(&amp;quot;scene1.wmv&amp;quot;));      &lt;br /&gt;video.StoryBoard.Add(      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Movie.FromFile(&amp;quot;scene2.mpg&amp;quot;));      &lt;br /&gt;view.Export(&amp;quot;myMovie.avi&amp;quot;);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;A lot of voodoo is going to have to happen behind the scenes to make this code work! But you will thank yourself when you need to implement the UI and your client developers will thank you when they want to generate a highly customized slide show or develop a nifty flip book drawing application. &lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-6177480679461732414?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/wfIOMfeSee4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/6177480679461732414/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=6177480679461732414" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/6177480679461732414?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/6177480679461732414?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/wfIOMfeSee4/imaginary-apis.html" title="Imaginary APIs" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/10/imaginary-apis.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CE4BRHo4fSp7ImA9WxRQFEg.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-865824141256761662</id><published>2008-10-08T01:01:00.001-07:00</published><updated>2008-10-08T01:02:35.435-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-10-08T01:02:35.435-07:00</app:edited><title>Simplicity belongs in APIs</title><content type="html">&lt;p&gt;In &lt;a href="http://blog.brandonbloom.name/2008/08/flexibility-belongs-in-apis.html"&gt;a recent post&lt;/a&gt;, I insisted that flexibility belongs in APIs.&lt;/p&gt;  &lt;p&gt;Simplicity also belongs in APIs. There are two key things to simplify in APIs:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Common tasks&lt;/li&gt;    &lt;li&gt;Surface area&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Since you are jam packing your APIs with loads of powerful and flexible functionality, you might have a lot of functions and data structures. You may have hoards of configuration options or capability bits, and a whole mess of operations to perform on them. However, your UI is simple and has picked sensible defaults for every operation. Your APIs should do the same. In fact, you should expose the same simplified operations your UI uses, so that developers do not have to guess what arguments you pass or in what sequence you composed more complex operations. If you have widely used private helper methods you've developed for your UI, you have identified common tasks for which your helper methods should be promoted to address.&lt;/p&gt;  &lt;p&gt;Developers should be unwittingly led to these simplified methods, they shouldn't have to go searching for them. For example, the .NET framework provides a significant number of overloads for opening a file. The most complex of these takes parameters for file access, sharing, creation behavior, and more. The simplest of these, just opens the damn file. Reading all of the lines from a file is a very common task that involves opening a file, iterating over each line, accumulating the results, and then closing the file. Or you could just call File.ReadAllLines and simply get an array of strings.&lt;/p&gt;  &lt;p&gt;ReadAllLines is completely implemented using StreamReader, but was placed in the File class instead of the StreamReader class because more people would look there first. Organize APIs by how naive developers would look for them, not experts. Remember that developers, like users, are perpetual intermediates. They are only going to write the code once (as beginners) and iterate on it several times until it works (as intermediates). After that, they will move on to something else. There won't be time for them to become experts.&lt;/p&gt;  &lt;p&gt;You have limited screen space for features, but you can always fit more APIs. You develop a great UI by removing the features you don't need. However, you develop a great API by avoiding adding the features that you don't need. If there is a set of parameters no one will ever change, remove the ability to change them. If there are multiple approaches to the same common task, choose one. The less surface area there is for a developer to search and understand, the more likely they are to quickly choose and implement an effective solution.&lt;/p&gt;  &lt;p&gt;In a future post, I will discuss my favorite technique for designing APIs.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-865824141256761662?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/hrwL0Cz_esk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/865824141256761662/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=865824141256761662" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/865824141256761662?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/865824141256761662?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/hrwL0Cz_esk/simplicity-belongs-in-apis.html" title="Simplicity belongs in APIs" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/10/simplicity-belongs-in-apis.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEEGR3c6fSp7ImA9WxRSFk8.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-1443271733181903144</id><published>2008-09-16T20:37:00.001-07:00</published><updated>2008-09-16T20:37:06.915-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-09-16T20:37:06.915-07:00</app:edited><title>Why so serious?</title><content type="html">&lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/SnprBoB86/SNB7IM68c5I/AAAAAAAAAP4/FgtlJkhh-Ng/s1600-h/image5.png"&gt;&lt;img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="225" alt="image" src="http://lh4.ggpht.com/SnprBoB86/SNB7LUty4GI/AAAAAAAAAP8/RRTYkxRQf8M/image_thumb1.png?imgmax=800" width="244" border="0" /&gt;&lt;/a&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Software doesn't need to be serious all the time. Of course, this applies to games, software toys, and consumer focused products, but it also applies to enterprise class software.&lt;/p&gt;  &lt;p&gt;Compare these two screen shots:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/SnprBoB86/SNB7QPbk1HI/AAAAAAAAAQA/jaU5BwB6frg/s1600-h/image2.png"&gt;&lt;img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="123" alt="image" src="http://lh3.ggpht.com/SnprBoB86/SNB7T6jbpqI/AAAAAAAAAQE/sGxg4IYvWuo/image_thumb.png?imgmax=800" width="244" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&amp;quot;Hurray, no spam here!&amp;quot; versus&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh6.ggpht.com/SnprBoB86/SNB7WkB7WGI/AAAAAAAAAQI/euLYaivvy8c/s1600-h/image8.png"&gt;&lt;img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="166" alt="image" src="http://lh6.ggpht.com/SnprBoB86/SNB7YgTnevI/AAAAAAAAAQM/grlFaYfL1bE/image_thumb2.png?imgmax=800" width="244" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&amp;quot;There are no items to show in this view.&amp;quot;&lt;/p&gt;  &lt;p&gt;Which message would you prefer to see each time you open your spam folder? Google's, of course.&lt;/p&gt;  &lt;p&gt;Does Google's choice of words cause you to question the quality of their software? Do they cause you to question whether or not you can do serious work with their tools? Of course not.&lt;/p&gt;  &lt;p&gt;Just because you are doing serious work, doesn't mean you need to be serious all the time. Research, anecdotes, and all the other evidence clearly show that a little bit of humor and personality injected into the workplace improves productivity and moral. Same goes for your software. Inject some personality. Include some humor. Everyone will be better off.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-1443271733181903144?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/IPcL6b2V2qA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/1443271733181903144/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=1443271733181903144" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1443271733181903144?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1443271733181903144?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/IPcL6b2V2qA/why-so-serious.html" title="Why so serious?" /><author><name>Brandon Bloom</name><uri>https://profiles.google.com/114179817210743283438</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-wzrCTMzz79I/AAAAAAAAAAI/AAAAAAAAAbU/srDiAY0bxMk/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh4.ggpht.com/SnprBoB86/SNB7LUty4GI/AAAAAAAAAP8/RRTYkxRQf8M/s72-c/image_thumb1.png?imgmax=800" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/09/why-so-serious.html</feedburner:origLink></entry></feed>

