<?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:thr="http://purl.org/syndication/thread/1.0" xmlns:gd="http://schemas.google.com/g/2005" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;D0QFRnc7eSp7ImA9WxFaFUo.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497</id><updated>2010-07-19T14:35:17.901-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><email>noreply@blogger.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>30</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;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="https://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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></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="https://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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></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="https://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=1471324358664389393" title="4 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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></author><thr:total>4</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="https://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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></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="https://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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></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="https://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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></author><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="https://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=1269333104236652268" title="6 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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></author><thr:total>6</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="https://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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></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="https://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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></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="https://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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></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="https://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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></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="https://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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></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="https://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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></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="https://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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></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="https://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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></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="https://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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></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="https://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><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/09/why-so-serious.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEYDRnY8cCp7ImA9WxRSEE4.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-1700806303399554203</id><published>2008-09-10T00:36:00.001-07:00</published><updated>2008-09-10T00:36:17.878-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-09-10T00:36:17.878-07:00</app:edited><title>Bug farms</title><content type="html">&lt;p&gt;I recently learnt a new &lt;a href="http://blogs.msdn.com/oldnewthing/archive/tags/Microspeak/default.aspx"&gt;microspeak&lt;/a&gt; phrase that I love: bug farm.&lt;/p&gt;  &lt;p&gt;Some features are bug farms.&lt;/p&gt;  &lt;p&gt;This means that these features are either poorly specified or highly unmaintainable. As a result, they produce a larger than average portion of the product's bugs. They can get into either state though a variety of ways, but by far the most common way is insufficient thought during design or implementation. Additionally, it is also possible to establish a bug farm by building a feature on top of an existing bug farm.&lt;/p&gt;  &lt;p&gt;If you encounter a feature which is covered in larvae, you should spray it with insecticides immediately. It is better to use too much insecticide and kill the feature than it is to deliver an infested product to your customers. That is, if the bugs don't rise up and eat you before you get the chance to do either.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-1700806303399554203?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/swbykj7ABNE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/1700806303399554203/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=1700806303399554203" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1700806303399554203?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1700806303399554203?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/swbykj7ABNE/bug-farms.html" title="Bug farms" /><author><name>Brandon Bloom</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/09/bug-farms.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0cDSHs-eCp7ImA9WxdaGUU.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-88165298186586634</id><published>2008-08-28T21:44:00.001-07:00</published><updated>2008-08-28T21:44:39.550-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-08-28T21:44:39.550-07:00</app:edited><title>Flexibility belongs in APIs</title><content type="html">&lt;p&gt;Software comes in a large spectrum of power and flexibility, as well as an equally large spectrum of complexity and usability. Users also come in a wide range of intelligence and skill. However, if you create software of average power and average complexity, you may satisfy the average users' needs, but they won't love you for it.&lt;/p&gt;  &lt;p&gt;One of my favorite books on software, &lt;a href="http://www.amazon.com/gp/product/0672326140?ie=UTF8&amp;amp;tag=youworkformec-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=0672326140"&gt;The Inmates Are Running the Asylum&lt;/a&gt;, describes the concept of &amp;quot;perpetual intermediates&amp;quot;. The book explains that beginners are only beginners for a little while, then they become intermediates. Most users are intermediates for as long as they use the software. Very few users become true experts. At any given time, virtually all of your users will be varying intermediates.&lt;/p&gt;  &lt;p&gt;With this powerful realization in mind, you may be tempted to design exclusively for these intermediates. When designing your software you can spin the flexibility and complexity dials to try to hone in this average user. Unfortunately, you would wind up pleasing more users by cranking these dials down much lower.&lt;/p&gt;  &lt;p&gt;The fundamental problem is that adding a bit of power and flexibility also adds a bit of complexity. Since most users are intermediates, they can tolerate some degree of complexity, but would honestly prefer not to. They avoid the complexity by only using the small subset of features they have come to understand and only venturing beyond their comfort zone when they absolutely have to. They quickly become intermediates and are no longer troubled by complexity.&lt;/p&gt;  &lt;p&gt;When users have to venture into uncharted features, they are effectively beginners, not intermediates. However, the more esoteric or powerful the feature, the more likely it was designed for that small percentage of experts. When the infrequent need arises for these features, users would rather force the problem into the features they know than learn new ones.&lt;/p&gt;  &lt;p&gt;Given that users actively avoid complexity and more powerful features, it becomes apparent that these powerful features should be excluded to keep complexity to a minimum. In doing so, you may annoy the 1% of your users who are truly experts and need the power. If you have a million users, 1% -- 10,000 users -- can not be forgotten. You need to give these users this power somehow.&lt;/p&gt;  &lt;p&gt;Put the power and flexibility into APIs! APIs enable power users to automate frequent tasks and create the unique functionality they need. As a bonus, new tools will pop up to fill needs you didn't even consider when you originally designed your software.&lt;/p&gt;  &lt;p&gt;&amp;quot;Wait a minute!&amp;quot; I hear your cry. &amp;quot;Expert users are not necessarily programmers&amp;quot;, you say. This is very true, but there are two factors which mitigate this problem.&lt;/p&gt;  &lt;p&gt;First, most expert users of any software have at least some technical ability with which to hack together solutions. If you don't believe me, take a look at the &lt;a href="http://www.langpop.com/"&gt;popularity of Visual Basic&lt;/a&gt;. Despite the relatively small number of professional software engineers using Visual Basic, it still has very strong search volume and book sales. This is presumably due to its inclusion in Microsoft Office. However, the &amp;quot;real&amp;quot; software engineers view it as not worth their time and these are the people producing technical content on the web. As such, there is less online discussion of the language. Even if an expert is unable to put something together, they probably know someone who can. And even if they don't know someone who can, then they probably have just identified a need to hire someone.&lt;/p&gt;  &lt;p&gt;Second, the needs of experts are so specialized that they need to exert a large amount of effort bending flexible software to their needs. Sometimes, no amount of flexibility will solve their problem. Other times, the flexibility is hidden behind so much complexity that it is not cost effective to uncover it. Still other times, users bend software until it breaks. Again looking at Visual Basic, it is quite common for Office users to create macros or scripts to solve problems that built in tools could have solved. Users do this because they didn't know they could or couldn't figure out how to utilize the existing features in the way they wanted to.&lt;/p&gt;  &lt;p&gt;The bottom line is that software should be &lt;a href="http://www.quotedb.com/quotes/1360"&gt;as simple as possible, not simpler&lt;/a&gt;. Treat users as intelligent, capable intermediates; do not engage in excessive hand-holding or assume expertise. Where applicable, provide plug-in or extension mechanisms, so that power and flexibility can be leveraged by APIs. Your users will love you for it.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-88165298186586634?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/xrYosuYPg1A" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/88165298186586634/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=88165298186586634" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/88165298186586634?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/88165298186586634?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/xrYosuYPg1A/flexibility-belongs-in-apis.html" title="Flexibility belongs in APIs" /><author><name>Brandon Bloom</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/08/flexibility-belongs-in-apis.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0IERXkzeSp7ImA9WxdaEkw.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-5883314086802481520</id><published>2008-08-19T22:51:00.001-07:00</published><updated>2008-08-19T22:51:44.781-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-08-19T22:51:44.781-07:00</app:edited><title>Updated feed url</title><content type="html">&lt;p&gt;If you are subscribed to my blog, please re-subscribe to:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://feeds.feedburner.com/YouWorkForMeComputer"&gt;http://feeds.feedburner.com/YouWorkForMeComputer&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Thanks!&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-5883314086802481520?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/bDSDfxJ8wmY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/5883314086802481520/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=5883314086802481520" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/5883314086802481520?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/5883314086802481520?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/bDSDfxJ8wmY/updated-feed-url.html" title="Updated feed url" /><author><name>Brandon Bloom</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/08/updated-feed-url.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEEGRn0ycCp7ImA9WxdaEU8.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-1065373778791265950</id><published>2008-08-18T23:17:00.001-07:00</published><updated>2008-08-18T23:17:07.398-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-08-18T23:17:07.398-07:00</app:edited><title>Computers are everywhere, you no longer have an excuse</title><content type="html">&lt;p&gt;I work in an environment full of brilliant, technically minded people. Try as I might, sometimes I simply forget how little most people know about computers.&lt;/p&gt;  &lt;p&gt;In IKEA, I saw a odd wire metal rack which mounted to the side of a desk and held a computer tower suspended off the ground. Why anyone would attach such an ugly thing to a desk is beyond me. What is wrong with the computer being four inches lower, on the ground?&lt;/p&gt;  &lt;p&gt;Moments later, an young, female employee pointed to the tower rack and referred to it as a &amp;quot;CPU holder&amp;quot;. Never mind that she was actually trying to sell this silly thing to someone, she just called a car engine by the name of a fuel injector!&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-1065373778791265950?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/I-wzXbsi1RI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/1065373778791265950/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=1065373778791265950" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1065373778791265950?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1065373778791265950?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/I-wzXbsi1RI/computers-are-everywhere-you-no-longer.html" title="Computers are everywhere, you no longer have an excuse" /><author><name>Brandon Bloom</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/08/computers-are-everywhere-you-no-longer.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DU4MQH88eSp7ImA9WxdXE0s.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-6385955388336458360</id><published>2008-06-24T21:25:00.000-07:00</published><updated>2008-06-24T21:26:21.171-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-24T21:26:21.171-07:00</app:edited><title>Microsoft manages interns better than Google</title><content type="html">&lt;p&gt;&lt;em&gt;In the interest of full disclosure: I was not offered a full-time position at Google. I was encouraged to apply again after earning a masters degree or after a few more years of experience. Please do not misconstrue this post as sour grapes; I mainly applied for a full-time conversion at Google to improve my Microsoft offer.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;span style="font-style: normal;"&gt;The title of this post is hyperbole. Both companies are full of brilliant engineers, managers, and HR staff. Both do an excellent job of managing interns; they really do care. However, my experiences at Microsoft made it impossible for me to choose Google. Both sets of experiences were overwhelmingly positive, but several individuals at Microsoft went that extra mile and won me over in a big way.&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;span style="font-style: normal;"&gt;Early during my first internship at Microsoft, it was apparent that my skills were being misappropriated. I wasn't having a whole lot of fun and my direct manager quickly identified the problem during one of our weekly 1-on-1 meetings. He radically adjusted the course of my internship to fit my skills and personality. Soon, things were going much better. I was more productive, having more fun, and learning a lot more.&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;span style="font-style: normal;"&gt;Great experiences didn't end with just my direct manager. Each intern also gets a mentor for additional job and team related support as well as a "coach" from a different part of the company to jump start your personal network. My mentor helped me understand the politics and personalities of our team and team members. My coach convinced me to tell my recruiter about the changes going on with my internship. My recruiter encouraged me to conduct informal interviews and have casual lunches with various people around the company. These "informationals" led to an awesome position for my second internship that was a perfect fit for me.&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;span style="font-style: normal;"&gt;Internships at Microsoft are as much about growing people as they are about getting engineering done. There are a lot of people around to support you, so you receive a lot of support. If you are lucky -- like I was -- all of these people are exceptional. Even if you are unlucky, at least one of the your many network seeds may be helpful. Additionally, recruiters are more personally involved. Our recruiter took us out for dinner and drinks in small groups and got to know us quite well.&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;span style="font-style: normal;"&gt;At Google, I once again felt as if my skills were being misappropriated, but enjoyed far less success in correcting it. For the majority of the internship, I only interacted with my direct manager. He lacked experience mentoring interns and was a little surprised when I raised concerns over the quality and pace of my own work. With no one else to turn to, I contacted my recruiter who was alarmed by my frustration. Although my manager and recruiter had the best of intentions, they choose to reassure me that I was doing good work rather than reposition me so that I could do outstanding work.&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;span style="font-style: normal;"&gt;I am more vocal and assertive than many engineers, and especially interns, at both Microsoft and Google. Speaking up at Microsoft did wonders for my career, but I didn't have to say a word for people to ask "what's the matter?" At Google, I spoke up and I felt as though I was the first to ever do so -- people were surprised and unsure how to react.&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;span style="font-style: normal;"&gt;It should be stressed that these were personal experiences. Some other fresh college graduate may have a completely symmetric experience.&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-6385955388336458360?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/dIM3ljXedvw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/6385955388336458360/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=6385955388336458360" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/6385955388336458360?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/6385955388336458360?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/dIM3ljXedvw/microsoft-manages-interns-better-than.html" title="Microsoft manages interns better than Google" /><author><name>Brandon Bloom</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></author><thr:total>3</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/06/microsoft-manages-interns-better-than.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0EERHoyeip7ImA9WxdSEUg.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-3351063354484207370</id><published>2008-05-18T17:03:00.001-07:00</published><updated>2008-05-18T17:06:45.492-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-05-18T17:06:45.492-07:00</app:edited><title>Rift</title><content type="html">&lt;p&gt;Rift is a top-down shooter with a time-stopping twist. It was written in XNA by three Drexel students in three days:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Brandon Bloom - Lead Programmer&lt;/li&gt;    &lt;li&gt;Lee Baker - Programmer and Sound&lt;/li&gt;    &lt;li&gt;Charbel El-Beyrouty - Artist&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Watch the video:&lt;/p&gt;  &lt;p&gt;   &lt;div class="wlWriterSmartContent" id="scid:5737277B-5D6D-4f48-ABFC-DD9C333F4C5D:2e528dbd-a2d5-4635-8f49-47e881ef6164" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;div&gt;&lt;object width="425" height="350"&gt;&lt;param name="movie" value="http://www.youtube.com/v/9UDHHzw7JI4&amp;amp;hl=en"&gt;&lt;/param&gt;&lt;param name="wmode" value="transparent"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/9UDHHzw7JI4&amp;amp;hl=en" type="application/x-shockwave-flash" wmode="transparent" width="425" height="350"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;&lt;label style="font-size:.8em;"&gt;(low quality, no sound, obvious recording artifacts -- I might have to buy Fraps)&lt;/label&gt;&lt;/div&gt; &lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Or download the game (with source):&lt;/p&gt;  &lt;p&gt;&lt;a href="http://brandonbloom.name/rift.zip"&gt;http://brandonbloom.name/rift.zip&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Hope you enjoy it!&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-3351063354484207370?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/jjU_j7cwl-0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/3351063354484207370/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=3351063354484207370" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/3351063354484207370?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/3351063354484207370?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/jjU_j7cwl-0/rift.html" title="Rift" /><author><name>Brandon Bloom</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></author><thr:total>1</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/05/rift.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A04GQ3kzfCp7ImA9WxdTFUo.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-1214155276154510333</id><published>2008-05-12T01:04:00.000-07:00</published><updated>2008-05-12T01:12:02.784-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-05-12T01:12:02.784-07:00</app:edited><title>A googol at Google</title><content type="html">Ok, well maybe not quite a Googol (1x10E^100), but certainly 2^63-1 (9,223,372,036,854,775,807). This quote was taken from &lt;a href="http://steve-yegge.blogspot.com/2008/05/dynamic-languages-strike-back.html"&gt;a talk by Steve Yegge&lt;/a&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;We overflowed a long at Google once.  Nobody thought that was possible, but it actually happened. &lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;Working at Google has irreversibly damaged my ability to reason about numbers. One moment you are talking about petabytes of data and the next moment you are talking about nanoseconds. It is truly mind numbing, but it has become hard not to laugh when someone talks about a huuuuge dataset of a few gigabytes or a &lt;i&gt;fast&lt;/i&gt; process at a few seconds.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-1214155276154510333?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/4833wF85c7E" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/1214155276154510333/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=1214155276154510333" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1214155276154510333?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/1214155276154510333?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/4833wF85c7E/googol-at-google.html" title="A googol at Google" /><author><name>Brandon Bloom</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/05/googol-at-google.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUIARnc4eSp7ImA9WxdTFEs.&quot;"><id>tag:blogger.com,1999:blog-4887447608343482497.post-876779103192875594</id><published>2008-05-10T14:26:00.001-07:00</published><updated>2008-05-10T16:52:27.931-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-05-10T16:52:27.931-07:00</app:edited><title>It is easier to pirate Windows than to install it legally</title><content type="html">My desktop machine is totally hosed. I don't know why or what is wrong, but it is really bad. So I want to re-install Vista, but I can't find the DVD or product key.&lt;br /&gt;&lt;br /&gt;I currently own several Windows Vista licenses:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Gift for being a Vista beta tester&lt;/li&gt;&lt;li&gt;Drexel CS Department&lt;/li&gt;&lt;li&gt;From interning at Microsoft for a combined 9 months (and full-time this July)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Microsoft Dreamspark&lt;/li&gt;&lt;li&gt;Probably more&lt;/li&gt;&lt;/ul&gt;I expected to use my beta tester Ultimate license via logging into &lt;a href="http://connect.microsoft.com/"&gt;connect.microsoft.com&lt;/a&gt;, downloading the DVD, and getting the product key from Connect as well. Unfortunately, I can't find the product key or download anywhere on the new Connect. Additionally, I'm blocked from all MSDN subscriber downloads because, well, I'm not a subscriber. Strangely enough, I vividly remember being able to download Vista from MSDN at one point...&lt;br /&gt;&lt;br /&gt;I'm looking high and low for a spot to download Windows Vista legally. It is &lt;a href="http://www.mininova.org/search/windows+vista+ultimate/seeds"&gt;laughably easy to download it illegally&lt;/a&gt;, and the download speed will be better than Microsoft's servers anyway.&lt;br /&gt;&lt;br /&gt;The next version of Windows should be freely available for download and the installer should ask for my Windows Live ID, not some cryptic 5x5 product key.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4887447608343482497-876779103192875594?l=blog.brandonbloom.name' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/YouWorkForMeComputer/~4/IdRquvF7P3Q" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.brandonbloom.name/feeds/876779103192875594/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=4887447608343482497&amp;postID=876779103192875594" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/876779103192875594?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4887447608343482497/posts/default/876779103192875594?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/YouWorkForMeComputer/~3/IdRquvF7P3Q/it-is-easier-to-pirate-windows-then-to.html" title="It is easier to pirate Windows than to install it legally" /><author><name>Brandon Bloom</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="10281291373750668260" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.brandonbloom.name/2008/05/it-is-easier-to-pirate-windows-then-to.html</feedburner:origLink></entry></feed>
