<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>BarneyBlog &#187; development</title>
	<atom:link href="http://www.barneyb.com/barneyblog/category/software-development/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.barneyb.com/barneyblog</link>
	<description>Thoughts, rants, and even some code from the mind of Barney Boisvert.</description>
	<lastBuildDate>Mon, 02 Mar 2020 13:20:35 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>The Importance of Development Environments</title>
		<link>https://www.barneyb.com/barneyblog/2011/12/20/the-importance-of-development-environments/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/12/20/the-importance-of-development-environments/#comments</comments>
		<pubDate>Wed, 21 Dec 2011 05:59:37 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[development]]></category>
		<category><![CDATA[tools]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1749</guid>
		<description><![CDATA[My first job as a software developer was in 1998, while I was an undergrad at the University of Arizona in Tucson, AZ.Â  The project was simple: build a JavaScript application to front a website that helped people incorporate a new business.Â  This was long before "ajax" became the new hotness, but I'd built full [...]]]></description>
			<content:encoded><![CDATA[<p>My first job as a software developer was in 1998, while I was an undergrad at the University of Arizona in Tucson, AZ.Â  The project was simple: build a JavaScript application to front a website that helped people incorporate a new business.Â  This was long before "ajax" became the new hotness, but I'd built full windowing environments in JS (supporting both NS4 and IE4) back in the mid '90s so this was old hat for me.Â  The client had a single environment.Â  Production.</p>
<p>This was not ideal.</p>
<p>My next job was after I'd dropped out of school, up in Portland, OR.Â  It was a small web development agency and we had a dedicated development server in our office which we worked on, and then separate production servers colocated offsite.Â  This was much better, but still meant we (the developers) stepped on each others toes all the time.</p>
<p>Getting better&#8230;</p>
<p>Next came working for a university on some internal project, still in Portland, OR.Â  I was the sole coder, and while I had my full development environment on my local machine, the most striking difference wasn't that it was local (and therefore fast and easy to screw with), but that I didn't have to compete with other devs' changes.Â  This was a big boon, and my productivity demonstrated it.Â  But still no version control, if you can believe it.Â  I still don't understand why that isn't forced on students in CS 101 before the first code project.</p>
<p>Almost there&#8230;</p>
<p>My first "real" job was up in Bellingham, WA with a company which sold communications management software.Â  The whole app was ColdFusion and sold with a SaaS model (about the same time as Salesforce.com was conceived).Â  There we not only had a production cluster, we had a staging environment, a dev (bleeding edge) environment, and per-developer environments on our local workstations.Â  It was here that I learned about version control, saw that it was a Good Thing<sup>(tm)</sup>, and put on my tantrum face and forced everything to halt until we had it implemented.Â  We used CVS, and then upgrade to SVN just after it went 1.0 (after watching it get closer and closer with bated breath).</p>
<p>We've got it!</p>
<p>My next job was back in good old Portland, OR.Â  I had to create SVN infrastructure, but no worries.Â  I knew what we needed, especially with six developers.Â  Also had to create local-workstation development environments, but again a completely worthwhile investment.Â  The trick was learning to leverage all of this, which brings me to the point.</p>
<p style="padding-left: 30px;">Every developer should be able to blindly and without regard for any consequences thrash their working codebase to see if random idea X works.</p>
<p>It has taken me a decade to really appreciate how important this simple idea is.</p>
<p>If you can't do this with your current development environment, you should invest some time in making it possible.Â Â  It means you <em>need</em> to be able to break any and every piece of code without affecting anyone else.Â  Including yourself if you need to fix some urgent bug all of a sudden.Â  It means you should be able to use version control if your idea takes 75 commits to test out, without affecting anyone else.Â  Including yourself as before.Â  It means that if your idea works, you shouldn't have to struggle to share your solution and get it into the next nightly.Â  Even if you're the only developer.</p>
<p>So what do you need?Â  You need (at least):</p>
<ol>
<li>version control which supports concurrent editing, branching and merging</li>
<li>a completely isolated instance of your software project with it's own working directory</li>
<li>the ability to create <em>n</em> copies of #2</li>
<li>a habit of small atomic commits (commit early, commit often)</li>
<li>the confidence in your setup to trust that no matter how bad you screw things up, you can always get back to any previous state whether it was five minutes or five months ago</li>
</ol>
<p>The first points is a no brainer, but is vitally important as it is the only way to get the remaining four items.Â  Points two and three are different sides of the same thing as #3 is really just #2 where you have two personalities (normal dev and crazy hacker dude).</p>
<p>Point four is a learned skill.Â  This isn't a piece of technology you adopt or something you do, it's a habit you consciously work to build because you want to be a better developer.Â  It's hard.Â  Everyone knows it's far easier to just take a pile of changes you've made and commit them with a message like "fixed stuff".Â  Suppress that urge.Â  Don't give in to the easy road.Â  Spend the time to fix one thing, ensure it works, ensure it's atomic, and commit it.Â  Then move on to the next thing.Â  If you're working on eight things at once, first go talk to your boss because they're clearly insane, and then think about point three and do each task in a separate working copy (perhaps even against a separate branch).Â  This might seem like pure hassle and no benefit, but it's the only way to make the investment you've made with points one to three actually pay off.Â  Version control repositories with non-atomic commits are certainly better than no version control at all, but the non-atomicity severely hamstrings their primary objective: the ability to act as a time machine.</p>
<p>Which brings me to the final point.Â  Confidence is a learned skill.Â  It comes partially from trying and succeeding, but it mostly comes from trying and failing and learning that your precautions keep you safe.Â  It's important to pick up random idea X and give it a whirl.Â  Really important.Â  And the only way to go about it is with the confidence you can recover from <em>anything</em>.Â  Anything.Â  If you don't have that confidence, you're going to go about it in a half-ass manner in case something unexpected comes up.Â  It's human nature; don't feel embarrassed.Â  What you have to realize is that because we are software developers, we have a distinct advantage.Â  We have a time machine.Â  We can go back in time, to any point in time, and start over fresh.Â  The lack of physical deliverables gives us this advantage, and we'd be fools to not take full advantage of it.</p>
<p>If you can't do that today, I strongly encourage you to think about ways you can get closer.Â  Don't worry about trying to get there in one step, just get closer.Â  Do that repeatedly and you'll get there, and every step closer has tangible benefit to future development.Â  It is worth every ounce of energy it takes to get there.Â  It's also important to look at new projects with the same mindset; setting yourself (and your fellow developers) up for this from the get-go is simple if you have the forethought to do it.</p>
<p>I have confidence in my development environment.Â  It's not perfect, but I have no qualms about ripping the heart out of a 300,000 line subsystem and seeing if I can put it back together.Â  That confidence means I can act.Â  I don't have to worry about consequences until I can reasonably weigh their ROI.Â  No more "what ifs".Â  Interestingly, every Star Wars nerd's favorite line is exactly wrong when applied to software: <span style="text-decoration: line-through;">"do or do not, there is no try"</span> "try, there is no do or do not".</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/12/20/the-importance-of-development-environments/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>.NET/Silverlight Developer Needed</title>
		<link>https://www.barneyb.com/barneyblog/2011/11/14/net-silverlight-developer-needed/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/11/14/net-silverlight-developer-needed/#comments</comments>
		<pubDate>Mon, 14 Nov 2011 16:37:51 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1746</guid>
		<description><![CDATA[The State of Oregon Public Health Department needs a rockstar .NET and Silverlight developer to customize a health and hospital information application.Â  The position is a six-month contract, and is on-site at the State offices in Portland.Â  There isn't an official job posting up yet (should be later this week), but if you want to [...]]]></description>
			<content:encoded><![CDATA[<p>The State of Oregon Public Health Department needs a rockstar .NET and Silverlight developer to customize a health and hospital information application.Â  The position is a six-month contract, and is on-site at the State offices in Portland.Â  There isn't an official job posting up yet (should be later this week), but if you want to send a resume I'll pass it along to the right people.Â  This looks like a really cool project, both from a technical perspective and from the impact it'll have on people's lives.Â  If not for the fact I already have a full-time job, I'd be all over this (including having to learn Silverlight in my spare time so I'd qualify).</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/11/14/net-silverlight-developer-needed/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Adobe, Mobile Flash Player, JavaScript, etc.</title>
		<link>https://www.barneyb.com/barneyblog/2011/11/10/adobe-mobile-flash-player-javascript-etc/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/11/10/adobe-mobile-flash-player-javascript-etc/#comments</comments>
		<pubDate>Thu, 10 Nov 2011 18:28:55 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[ajax]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[flex]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[tools]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1743</guid>
		<description><![CDATA[Before you click over this because you know me as a Flash hater, give me two seconds.Â  That's not what this post is about.Â  It's about a larger issue.Â  It's about how awesome it is to be a web developer these days.
Ten years ago, being a web developer sucked.Â  Deployment was easy (rsync to production [...]]]></description>
			<content:encoded><![CDATA[<p>Before you click over this because you know me as a Flash hater, give me two seconds.Â  That's not what this post is about.Â  It's about a larger issue.Â  It's about how awesome it is to be a web developer these days.</p>
<p>Ten years ago, being a web developer sucked.Â  Deployment was easy (rsync to production and done), but the tooling available to us was dismal.Â  And I mean crying-naked-in-a-snowstorm dismal.Â  Browsers were inconsistent, their built-in programming environment (read JavaScript) was reasonably functional but horridly slow, and hardware wasn't beefy enough to deal with scripting languages for hard-core number crunching.</p>
<p>But we had the Flash Player.Â  Flash provided an environment that beat all three of those problems, and beat them soundly.Â  It was consistent across browsers and operating systems, used a similar language to what we were used to (both JavaScript and ActionScript are ECMAScript implementations), and it let the developer compile the script into something a little lower level to run in a dedicated VM on the user's machine which meant it was faster.Â  Of course, Flash is an animation toolkit, but we figured out how to bastardize it with the single-frame movie with a include-and-stop script in it so we could build applications entirely with script (like we wanted), but leverage the Flash player to actually run them.Â  Not to mention the rich support for visual stuff.Â  All this led to the concept of a RIA (Rich Internet Application).Â  Something similar to what we had on the desktop, but deployed to the web with all the benefits (and some drawbacks) that has.</p>
<p>Then browsers got their act together.Â  We started seeing a unifying focus on application development with the browser as the environment.Â  People got serious about fast JavaScript runtimes.Â  Standards were written (e.g., CSS2/3, Canvas) and largely adhered to.Â  And hardware got faster.Â  JavaScript application frameworks (EXT, YUI, GWT, etc.) showed up to leverage all that, and now it was possible to build RIAs using standards supported by a wide array of vendors.</p>
<p>In order to compete with this, Adobe released Flex, which is an application development framework for deploying to Flash Player.Â  It was horridly expensive, difficult to work with, had all kinds of implementation problems, but was better than what you got with JavaScript.Â  For a while.Â  Unfortunately a single software company, even one of the largest in the world, couldn't hope to compete with the widespread interest and momentum around browser-native RIA development.Â  Flex died as a web application framework pretty much before it was released.Â  Which isn't to say it wasn't used (it was and still is), but the browser RIA juggernaut crushed it like a bug.</p>
<p>Then we saw the huge surge in mobile devices.Â  It started with smartphones and now includes tablets and e-readers of various form factors.Â  Fortunately for both consumers and manufacturers, the web wasn't new, so they were able to jump right on top of all the standards and browser capabilities which had been created for the desktop.Â  A huge market segment opened up for web application developers and browser-native was reasonable right out of the box.</p>
<p>Adobe again tried to compete by creating Mobile Flash Player, but the benefits of Flash Player are small on new mobile devices, especially considering that so much of a mobile device experience is through web-connected native applications, not traditional web applications.Â  And here we have Adobe's smartest move yet around Flash Player: killing it for the mobile market in favor of restructuring the ecosystem around using it as a development environment for native applications.</p>
<p>Unfortunately, I don't think it's going to matter because Flash is still really slow and heavy and it's not really that much better to develop with than the truly native dev kits.Â  Yes, it offers the promise of cross-platform deploy, but just as Java demonstrated 10-15 years ago, that isn't much of a selling point for a single application.Â  People expect not just native execution, they expect native idioms, which means you have to develop for a specific platform, even if you're using a cross-platform toolkit.</p>
<p>So where does that leave us?Â  As consumers it leaves us in a great spot: there are lots of ways developers can deliver engaging applications to us, on all our devices.Â  As developers it leaves us in a weird spot: we're stuck with being either web or native developers, with Flash now trying to occupy a sort of middle ground (develop web-style, deploy native).Â  On the desktop I think Flash (via AIR) holds some promise, but ultimately I don't think it'll last.Â  The platform just isn't compelling enough to justify the dedication it requires to use it.Â  Java was designed for very much the same purpose as AIR, and virtually no one uses it.Â  Java moved almost entirely server side, something which Flash isn't likely to do.</p>
<p>Most interesting is the PhoneGap/Titanium/etc. movement which is very much paralleling the browser resurgence I talked about earlier.Â  Huge communities of people are working to take all the skills we have as web application developers and give us a build process to take web-ish apps and compile them into native applications, in much the same way Adobe has use AIR to compile Flash into native apps.Â  However, I think Flash is going to lose in exactly the same way, and for exactly the same reasons, as it lost in the browser.</p>
<p>Bottom line, if you use the web or a web-connected device (read: everybody) the world is going to be glorious in a couple years and only get better.Â  If you're a developer trying to work in that space, you need to learn browser technologies.Â  It's the way of the future.Â  Flash had it's run, but it's been on the way out for a long while.Â  It'll stick around, just like COBOL and Fortran have, but without an alternative path like Java ended up taking it isn't going to stay relevant to mainstream developers.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/11/10/adobe-mobile-flash-player-javascript-etc/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Syncing Files on the Cloud</title>
		<link>https://www.barneyb.com/barneyblog/2011/08/18/syncing-files-on-the-cloud/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/08/18/syncing-files-on-the-cloud/#comments</comments>
		<pubDate>Fri, 19 Aug 2011 05:41:12 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1717</guid>
		<description><![CDATA[A couple weeks ago, I gave my "Replace Your Iron With A Cloud" talk from cf.objective to the CFMeetUp.Â  If you didn't catch it, you can view the recording on that page.Â  Several people both during and after the presentation had questions about syncing files between servers.Â  This doesn't really have anything to do with [...]]]></description>
			<content:encoded><![CDATA[<p>A couple weeks ago, I gave my "Replace Your Iron With A Cloud" talk from cf.objective to the <a href="http://www.meetup.com/coldfusionmeetup/events/25887911/">CFMeetUp</a>.Â  If you didn't catch it, you can view the recording on that page.Â  Several people both during and after the presentation had questions about syncing files between servers.Â  This doesn't really have anything to do with hosting in the cloud in particular, as you'll have the same problem with multiple physical servers, but the cost effectiveness of cloud computing is often the means by which smaller applications move from a single (physical) server to multiple (cloud) servers.</p>
<p>Due to those questions, I wanted to run through a few points on the syncing issue here, rather than continuing to answer individual emails/tweets/etc.Â  I'm going to talk about a hypothetical blogging application, as blogs are simple and well understood, and at least for this discussion, illustrate all the interesting bits and pieces.</p>
<blockquote><p>Before we get started, if you're not using version control, stop reading, go set it up (either yourself or through one of the myriad free/cheap hosting providers), and check your code in.  I like Subversion.  Git seems to be the current hotness.  Stay away from CVS and Visual SourceSafe.  Then you can come back and read the rest of this.</p></blockquote>
<p>First and foremost, every piece of data has a single canonical home.Â  That is, without a doubt, the most important thing to keep in mind.Â  So what is the canonical home for these data?Â  It depends on the type, and as I see it, there are four main ones (with their canonical home):</p>
<ol>
<li>server configuration: version control (unless you're Windows/IIS/etc., then you have to be more creative)</li>
<li>source code and assets: version control</li>
<li>user data (e.g., blog posts): your database cluster (you should think of it as a cluster, even if there is only one node)</li>
<li>user assets (e.g., uploaded photos): in the database or on a dedicate hosting service (your own, or third party)</li>
</ol>
<p>Most of those places aren't very useful for running your application.Â  So you invariably copy your data around, caching it in various locations for various purposes.Â  What you have to remember is that every copy of any piece of data outside the canonical home is exactly that: a copy.Â  If you change it, it doesn't mean anything until the change is propagated back to the canonical home.Â  Think about your source code.Â  You're happily hacking away on your local development machine and build some awesome new feature.Â  That's well and good, but it doesn't "count" until it's been checked into version control.Â  Your local box is a copy of the data, not the real data.Â  That same mindset applies for <em>all</em> data, regardless of type.</p>
<p>Let's say our blogging app runs on three application servers atop a two-node database cluster.Â  We'll assume the cluster is already doing it's job of keeping data synced between the nodes, handling failover if there is a problem, etc.Â  The first thing is server config.Â  How do we ensure all three application servers are configured identically?Â  We need to push a copy of the configuration from the canonical home to each server.Â  Then we know they're identical.Â  When we need to update configuration, we have to do the same thing.Â  You make and test the changes somewhere (hopefully on a test site, but perhaps on one of the production boxes), push those modifications back to the canonical home, and then update each server's copy of the data.Â  Do NOT, under any circumstances, log into each box and manually make the same configuration change on each one.Â  It'll work, as long as you're perfect, and nobody is perfect.</p>
<p>Now that we now our application servers are all configured the same, the next task is to ensure the code is the same across all of them.Â  You could (and might want) to consider your code to be part of your server configuration, but for right not lets assume you have servers which will be configured once and run multiple sequential versions of the application.Â  The best way to get your code from version control to your servers is via a single button click.Â  This isn't necessarily practical for all situations, but it is an ideal.Â  The approach I typically take is two button clicks: one to build a release from the current development branch and put it into a special area of the version control repository, and the second to take a release and deploy it to one or more of the production servers.Â  Note that the "build" process might be nothing more than copying files if you have a simple app, but more likely will at least involve aggregating and compressing JS/CSS resources and possibly compiling code.</p>
<p>As for actually getting the code to your servers, my tool of choice is rsync.Â  It does differential pushes, so if you have a 400MB app (like we do at Mentor) you can still push out releases quickly since most of that won't change every time.Â  Rsync runs on all major platforms, and doesn't have issues crossing platform boundaries.Â  It's there by default on pretty much all modern *nixes, and it's simple to set up on Windows with one of various prebundled packages.</p>
<p>Alright, now we can be confident that our app servers are all synchronized for the developer data portions of our full data set.Â  That's the easy part.Â  The user data is a bit trickier.Â  For the blog posts, comments, user accounts, etc. the solution is simple.Â  We have a single database cluster that all of our app servers talk to in realtime for reads and writes.Â  The database cluster will take care of consistency and availability through whatever magic is needed and supplied by the database vendor.Â  My typical configuration, just for reference, is two or more MySQL servers running circular (master-master) replication.Â  Whatever your platform is, you can pretty much get the equivalent.</p>
<p>If you have huge data volume and/or large scaling capacity, this approach probably isn't ideal.Â  You'll need to look at a distributed database of some sort, either sharding a relational database, or use a more purpose-specific database (e.g., <a href="http://cassandra.apache.org/">Apache Cassandra</a>) which is designed around a distributed store.Â  I'm not talking about these use cases.Â  If you have this kind of problem, hire someone to help solve it.Â  At that scale there is no such thing as a general purpose solution.Â  General tools, yes, but the specific use of them is bound tightly to your application's needs.</p>
<p>Now the worst of the four: user assets.Â  Why are these bad?Â  Because they're typically accessed directly.Â  For example, if a user uploads a photo to display in their blog post, the web browser wants to request that image after it gets the page with the post in it.Â  Which means with our three app servers, when we put the post into the database with the image reference, we need to get the image itself onto the other two application servers.Â  This is the wrong approach.Â  Remember the canonical home?Â  That can't be three places, it has to be one.</p>
<p>The most obvious might be to just stick the image into a BLOB field in the database.Â  That certainly works, it's tried and true, and it doesn't require any new infrastructure.Â  However, it means that every request for an image had to go through your application code to make a SQL query to get the bits and then stream it back to the browser.Â  That can be a big performance issue, especially if you have large files, as while the bits are streaming out, you're consuming both a database connection and an app server thread.Â  If it takes the browser 20 seconds to pull down a huge image and you're burning a thread and a connection for that entire duration, you're going to run into scalability issues very quickly.Â  However, if you have low concurrency, this is a perfectly viable approach.</p>
<p>A much better solution, especially for assets which need to be served back to clients directly, is to set up a static file hosting "cluster" analogous to the database cluster.Â  That means one or more nodes which will internally take care of data synchronization amongst them.Â  This may seem like we're back to the same "sync the file across the app servers" problem, but it's not.Â  The difference is that the application now has a single place to stick something (the cluster) so it doesn't care about replicating it for durability/scalability.Â  We can separate that concern out of the application and into a dedicated system.Â  In effect, this is just another database system, except it's managing filesystem assets instead of tables of data, but we don't care too much. It's the separation that is important.</p>
<p>This sort of model also allows multiple applications to interface with the same assets far more easily than if the assets are stored within an application's database.Â  Say this blogging application is really successful and you want to spin off a photo sharing site.Â  It'll be a separate application, but you want to let users easily leverage their blog photos on the photo sharing side, and be able to easily insert photos from their galleries into their blog posts.Â  By having the images stored outside of the blogging application, it becomes far more easy to reuse them.</p>
<p>And this brings us to my favorite of the cloud services from Amazon: Simple Storage Service or S3.Â  S3 is exactly what I've outlined in the two previous paragraphs.Â  It's incredibly inexpensive, ridiculously scalable, and has interface libraries available for pretty much every major language you could want.Â  It's simple to use, supports very fine grained security (should you need to have private or semi-private assets), and just generally solves a whole bunch of nasty problems.</p>
<p>So to wrap this all up, I'm going to run through the flows:</p>
<p>First, when we want a new server, we configure it from our repository.Â  If we need to change the configuration, we change it in the repository and push it out to all the servers.</p>
<p>Next, when we want to change the application, we make our mods and commit it to the repository, and then use an automated process (which, I might add, should be configured from version controlled configuration) to build a release and push releases out to our application servers.</p>
<p>When a user is using our application, we'll take any data they give us and place it in a single location for use by all application servers.Â  In the case of simple data (numbers, strings, etc.) it'll go into some kind of database.Â  For binary data (images, zips, etc.) it'll go into an asset store.Â  In either case, all accesses and modifications to the data by the application are directed at a single repository, so there is no need to do synchronization.</p>
<p>Oof.Â  That's a lot.</p>
<p>The last thing is to consider caching.Â  For this particular example, let's assume we're going to stick our blog uploads into the database.Â  We don't have the resources/ability/time to adopt something like S3, and the limitations on using the database for BLOB storage are acceptable.Â  So how can we optimize?Â  Well, the easiest thing is to make BLOBs immutable (so if you want to upload a new version of something, you'll get a new BLOB rather than update the existing BLOB, and the application will track lineage), and then you can cache them wherever you want without having to worry about change.Â  For example, you have your images at URLs of the style /files/?id=123.Â  When a request comes in, the application server looks on it's filesystem for the image to stream back.Â  If it's not there, it'll query the database to get it, and then both write it to the filesystem and stream it out.Â  On the next request, the file will already be there, so the database won't be queried.</p>
<p>We can do better than that, though, because we really don't want the application server involved on those cached requests.Â  By using something like Apache HTTPD's mod_rewrite, we can have URLs like /files/123.jpg.Â  When a request comes in and the file exists, Apache will stream it back without involving our application at all. If the file doesn't exist, Apache will rewrite it to something like /files/?id=123 which will do the same thing as before: query from the database, save to the filesystem for future requests, and stream the file back to the browser.</p>
<p>Why did I stress immutability before?Â  Because this mechanism, while very simple, has no way of detecting if a locally cached file has been updated in the database.Â  There are various ways to get around this issue, and I'm not going to go into details.Â  What's important is that user data, just like your source code, doesn't have to be used <em>directly</em> from it's canonical home &#8211; it can absolutely be copied around for whatever reason (usually performance) as long as you still have a single canonical home for it.</p>
<p>This has now morphed into an incredibly long post.Â  I probably ought to have broken it up into several pieces.Â  Perhaps next time.Â  :)</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/08/18/syncing-files-on-the-cloud/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Visiting Recursion</title>
		<link>https://www.barneyb.com/barneyblog/2011/06/16/visiting-recursion/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/06/16/visiting-recursion/#comments</comments>
		<pubDate>Thu, 16 Jun 2011 21:32:34 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[cfml]]></category>
		<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1698</guid>
		<description><![CDATA[Recursion is a very powerful technique, but when processing certain types of data structures you can end up with problems.Â  And not the "compiler error" kind of problems, the bad semantic kind.Â  Fortunately, at least one class of them is pretty easy to solve with visit tracking, though it's a subtle solution.Â  But I'm going [...]]]></description>
			<content:encoded><![CDATA[<p>Recursion is a very powerful technique, but when processing certain types of data structures you can end up with problems.Â  And not the "compiler error" kind of problems, the bad semantic kind.Â  Fortunately, at least one class of them is pretty easy to solve with visit tracking, though it's a subtle solution.Â  But I'm going to build up to that from bare-bones recursion first.</p>
<p>As you know, a recursive algorithm is one which splits the problem into two parts: the base case and the recursive case.Â  The base case is a fixed solution to a simple form of the problem the algorithm is designed to solve.Â  The recursive case, on the other hand, is a dynamic solution which is defined as "a little work plus the solution to a simpler form of the same problem".Â  That simpler form of the problem is solved by reinvoking the recursive algorithm, and eventually reaches the base case (which breaks the cycle).Â  Consider computing factorials, which is the de facto standard for discussing recursion:</p>
<pre>function factorial(n) {
  if (n &lt; 0)
    throw 'NegativeArgumentException'
  return <span style="color: #ff0000;">n == 0 || n == 1</span> ? <span style="color: #008000;">1</span> : <span style="color: #0000ff;">n * </span><span style="color: #3366ff;">factorial(n - 1)</span>
}
</pre>
<p>Here the <span style="color: #ff0000;">conditional</span> is used to pick between the <span style="color: #008000;">base case</span> and the <span style="color: #0000ff;">recursive case</span>.Â  As you can see, the recursive case is defined by a <span style="color: #0000ff;">small bit of work</span> (multiplying by 'n') and then <span style="color: #3366ff;">solving a simple form of the problem</span> (the factorial of n &#8211; 1).Â  The most important thing to note is that the recursive case is guaranteed to eventually recurse down to the base case (or raise an exception).Â  That means infinite recursion is impossible, which is good.Â  Now onward to the interesting stuff&#8230;</p>
<p>Consider this data structure:</p>
<pre>barney = {
  "name": "Barney",
  "sex": "male",
  "dob": "1980-06-10",
  "children": [
    {
      "name": "Lindsay",
      "sex": "female",
      "dob": "2004-01-09",
      "children": []
    },
    {
      "name": "Emery",
      "sex": "male",
      "dob": "2005-08-12",
      "children": []
    }
  ]
}
</pre>
<p>This is a hierarchical data structure (i.e., a tree), where there is a single root node (the structure named "Barney") and then some number of child nodes.Â  We can see here that there are only two levels ("Barney" and "Barney's children"), but you can imagine here in 40 years that there will very likely be at least one more level, probably two more.Â  The point is that we don't know how deep the structure is, but we do know that it isn't infinitely deep.Â  It has to end somewhere.</p>
<p>How could I find the average age of everyone?Â  Well, I'd use recursion to do it, but if you think about what we need to do, you'll quickly see there's a problem.Â  Computing an average has to be done after all the aggregation is complete &#8211; you can't average subaverages and have it come out right unless you also weight the subaverages.Â  So we'll need to either sum the total age and divide by the number of people or compute subaverages and track the number of people "within" them.Â  I'm going to take the first approach.Â  Here are my functions:</p>
<pre>function averageAgeInSeconds(family) {
  var <span style="color: #008000;">result = {
    totalAge: 0,
    count: 0
  }</span>
  averageAgeParts(family, <span style="color: #008000;">result</span>)
  return Math.round(<span style="color: #008000;">result.totalAge</span> / <span style="color: #008000;">result.count</span>)
}
function averageAgeParts(family, <span style="color: #008000;">result</span>) {
  for (var i = 0, l = family.length; i &lt; l; i++) {
    <span style="color: #008000;">result.totalAge</span> += getAgeInSeconds(family[i])
    <span style="color: #008000;">result.count</span> += 1
    <span style="color: #3366ff;">averageAgeParts(family[i].children, <span style="color: #008000;">result</span>)</span>
  }
}
function getAgeInSeconds(person) {
  return Math.floor((new Date().valueOf() - Date.parse(person.dob)) / 1000)
}
</pre>
<p>As you can see, I'm creating a '<span style="color: #008000;">result</span>' structure for storing my aggregates, then using the 'averageAgeParts' helper function which is where the <span style="color: #3366ff;">recursion </span>happens.Â  After the aggregates are done, I'm doing the division in the main function to get the average.Â  Note that I don't have an explict base case anywhere.Â  The reason is that it's impossible for the data structure to be infinitely deep; I'm relying on that fact to act as my base case and let the recursion bottom out.Â  In more direct terms, at some point the loop inside averageAgeParts will be traversing an empty array (e.g., Lindsay's children), which means the recursive call will not be invoked (the base case).</p>
<p>Just for reference, the average age is about 14.7 years.</p>
<p>The important thing to note here is that my main function isn't directly recursive.Â  Instead it delegates to a recursive helper method and supplies some additional context (the '<span style="color: #008000;">result</span>' variable) for it to operate on while wakling the tree.Â  This extra stuff is critical for solving a lot of problems with recursion.Â  I'm not going to show the implementation, but consider how you'd change this so you could request the average age of a family, but constrain it to only females (be careful to ensure you count female descendents of males).Â  How about if you wanted to allow counting a certain number of generations (regardless of the total tree depth)?</p>
<p>Now on to the whole point: visitation.Â  The data structure I've shown to this point is a tree, as we discussed.Â  But it doesn't represent the real world: a child has two parents, not one.Â  That's no longer hierarchical, so we can't stick with a tree, we need to generalize into a graph.Â  (Just to be clear, trees <em>are</em> graphs, but with the extra constraint of hierarchy.)Â  So how might our structure look now?Â  First of all, we can't represent it with a literal; we'll need to define a tree structure first and then add some extra linkages to "break" the tree nature and revert it to just a graph.Â  Using the 'barney' object from above, here's how our graph might look.</p>
<pre>boisverts = [
  barney,
  {
    "name": "Heather",
    "sex": "female",
    "dob": "1980-02-12",
    "children": Array.concat(barney.children)
  }
]
</pre>
<p>The last line creates a 'children' array for Heather which contains the same contents as Barney's 'children' array (but is a separate array).Â  Now we can access Emery as either boisverts[<strong>0</strong>].children[1] or as boisverts[<strong>1</strong>].children[1] and it's the same object.Â  That's important: it's what makes this a graph instead of a tree.Â  So now what happens when we run our averageAgeInSeconds function on 'boisverts'?Â  It'll run just dandy, but while the correct answer is 18.9 years, the result will be 14.8 years.Â  The reason is that it'll count Emery and Lindsay twice (once as Barney's children and again as Heather's children).</p>
<p>What we need is some way to keep track of what we've already processed (visited) so we can avoid processing stuff multiple times, and we can do that with a new subkey in our existing 'result' structure:</p>
<pre>function averageAgeInSeconds(family) {
  var result = {
    totalAge: 0,
    count: 0<span style="color: #008000;">,
    visited: []</span>
  }
  averageAgeParts(family, result)
  return Math.round(result.totalAge / result.count)
}
function averageAgeParts(family, result) {
  for (var i = 0, l = family.length; i &lt; l; i++) {
    <span style="color: #008000;">if (visit(family[i], result)) {</span>
      result.totalAge += getAgeInSeconds(family[i])
      result.count += 1
      averageAgeParts(family[i].children, result)
    <span style="color: #008000;">}</span>
  }
}
<span style="color: #008000;">function visit(o, result) {
  for (var i = 0, l = result.visited.length; i &lt; l; i++) {
    if (result.visited[i] === o) {
      return false
    }
  }
  result.visited.push(o)
  return true
}</span>
</pre>
<p>What I've done is create a 'visit' function to keep track of which objects have been visited.Â  Now 'averageAgeParts' uses 'visit' to check if it should visit (process) an object.Â  I've implemented 'visit' with an array as I wanted illustrative clarity not performance.Â  <span style="text-decoration: underline;">In the real world you'd use a hashtable instead of an array so you'll have O(n) peformance instead of O(n</span><sup>2</sup><span style="text-decoration: underline;">).<br />
</span></p>
<p>Now when we run this we'll get 18.9 years, as expected, because even though Emery and Lindsay will be iterated over twice, they'll only be processed the first time.Â  More specifically, the 'visit' function will return false on the second pass so the body of the loop will be skipped and their values won't be added to the aggregates the second time.</p>
<p>This sort of problem crops up all the time in computer science, and while I've shown it here with recursion, it's not necessarily tied to recursive algorithms.Â  One of the ones which is quite common, and fortunately "below the radar" for a lot of people, is garbage collection in modern virtual machines.Â  Another place is with serializing objects, either for permanent storage or transport across the wire.Â  The specific solutions are different, but they all end up using some sort of visit tracking on the graph nodes or edges.</p>
<p><strong>NB:</strong> This post was prompted by a discussion on the <a href="http://groups.google.com/group/taffy-users">Taffy users mailing list</a>, but as it has myriad other implications, I've discussed the topic in a more general way.</p>
<p><strong>NB:</strong> All code is JavaScript, and should run in any reasonable environment.Â  I did all my testing in Firefox 3.5, but I'd expect this to work back in Netscape 4.7 and IE 4.</p>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow: hidden;">http://groups.google.com/group/taffy-users</div>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/06/16/visiting-recursion/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Need A Job?</title>
		<link>https://www.barneyb.com/barneyblog/2011/05/26/do-you-need-a-job/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/05/26/do-you-need-a-job/#comments</comments>
		<pubDate>Thu, 26 May 2011 23:54:11 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[coldfusion]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[personal]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1695</guid>
		<description><![CDATA[I have a need.Â  A pressing need.Â  I need new coworkers.Â  Three of them.
Boring stuff first.Â  Mentor Graphics Corp is the EDA industry leader and makes all kinds of crazy design and simulation software for electrical engineers, and now embedded developers.Â  If you have a cell phone or a computer, it's almost guaranteed that you're [...]]]></description>
			<content:encoded><![CDATA[<p>I have a need.Â  A pressing need.Â  I need new coworkers.Â  Three of them.</p>
<p>Boring stuff first.Â  Mentor Graphics Corp is the EDA industry leader and makes all kinds of crazy design and simulation software for electrical engineers, and now embedded developers.Â  If you have a cell phone or a computer, it's almost guaranteed that you're using something which either embeds Mentor IP or was designed/tested/manufactured with the help of Mentor products.Â  The company is about a billion dollars in annual revenue and publicly traded.Â  Our team is currently seven application developers plus a designer, a  front-end developer, a multimedia guy, a content editor, and a couple of  operations people.Â  Plus Ron, our boss.Â  We're hiring three more  application developers.Â  The positions are full-time, on-site, in  Wilsonville OR.Â  Blah, blah, blah.</p>
<p>I work in the corporate marketing department (which is weird, but bear with me) on the web services team which is responsible for pretty much all the public-facing web infrastructure for the company.Â  <a href="http://www.mentor.com/">www.mentor.com</a> is the primary property, but we run the support portal, an internal business intelligence application, corporate blog hosting, and a couple industry journal sites.Â  We own everything that comes after the business requirements; divisions in the company come to us with business problems, and we spec, architect, implement, and host the solutions.Â  Despite the fact we're in the marketing department, IT comes to us for expertise.Â  Our primary platform is Adobe ColdFusion, but we support Groovy, Java,  PHP, and Python for different applications where there was a compelling case to not choose a CFML-based solution.</p>
<p>Now the interesting stuff.</p>
<p>There is a formal job description somewhere, but I don't know where, and I wouldn't link to it even if I did.Â  If you want to work here, you need two things:</p>
<ul>
<li>technical competence</li>
<li>a passion for development</li>
</ul>
<p>We expect you to be able to not only implement a spec, but also design solutions.Â  You shouldn't have to be reminded that not everyone speaks English and that clocks around the world don't all say the same time.Â  You should know why pure functional languages don't have <tt>synchronized</tt> blocks.Â  You should have a preference between <tt>emacs</tt> and <tt>vi</tt> and be able to articulate in two sentences or less.Â  You should have commits to some public VCS repository somewhere.Â  You want to be part of a large non-hierarchical team working on a variety of projects.Â  You should know what MVCC stands for without looking it up.Â  You should want to help the six-months-from-now you have less pain, even if it takes a little more work today.Â  You shouldn't be proficient in all of Java, C#, JavaScript, Python, Ruby, and Lisp.Â  You should be proficient in at least one of them, and be competent in at least one more, and be able to compare and contrast the merits of the rest.</p>
<p>If that sounds like you, drop me an email at barney_boisvert@mentor.com or bboisvert@gmail.com.Â  Ron wants a resume, but I want two paragraphs and ~30 lines of interesting code.Â  Not 40, not 20.</p>
<p>Our hiring process is straightforward: get your info, quick mini-interview via phone, a simple code test, and an on-site interview to  meet the team.Â  Mentor pays competitively, and has exceptional benefits  beyond the salary.Â  Positions are full time, relocation assistance is  likely available if you're not in the area &#8211; working remote is not an option.Â  Ron is very reasonable: if  you're the right person, he'll go to bat for you/him/me to ensure things  work.Â  Everyone here understands that spending a little cash to get the right people pays off every time.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/05/26/do-you-need-a-job/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Even Better Inline Progress Bars</title>
		<link>https://www.barneyb.com/barneyblog/2011/05/17/even-better-inline-progress-bars/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/05/17/even-better-inline-progress-bars/#comments</comments>
		<pubDate>Tue, 17 May 2011 17:29:18 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[css]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[javascript]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1680</guid>
		<description><![CDATA[In the handful of months since I last posted about inline progress bars, I've made some enhancements to the mechanism. In particular, the original mechanism didn't handle overages very gracefully, and while overage isn't strictly needed for progress, I've ended up wanting the same inline display for other types of measurements (e.g., fundraising dollars against [...]]]></description>
			<content:encoded><![CDATA[<p>In the handful of months since I last posted about <a href="http://www.barneyb.com/barneyblog/2010/09/15/inline-js-progress-bars/">inline progress bars,</a> I've made some enhancements to the mechanism. In particular, the original mechanism didn't handle overages very gracefully, and while overage isn't strictly needed for progress, I've ended up wanting the same inline display for other types of measurements (e.g., fundraising dollars against a goal).</p>
<p>The JS has grown slightly more complicated, creating a richer structure to style to handle some of the new stuff:</p>
<pre>jQuery(".progress").each(function() {
  var $this = jQuery(this)
  var frac = parseFloat($this.attr("rel"))
  if (isNaN(frac)) {
    return // bad data
  }
  var html = $this.html();
  $this.empty()
    .append(''
      + '&lt;span class="container"&gt;'
        + '&lt;span class="status-text-spacer"&gt;' + html + '&lt;/span&gt;'
        + '&lt;span class="progress-bar" style="width:' + (frac * 100) + '%;"&gt;&lt;/span&gt;'
        + '&lt;span class="status-text" style="width:' + Math.max(100, frac * 100) + '%;"&gt;'
          + '&lt;span class="status-text-spacer"&gt;' + html + '&lt;/span&gt;'
        + '&lt;/span&gt;'
      + '&lt;/span&gt;'
    )
  if (frac &gt;= 1) {
    $this.addClass("overage") // strictly for external targetting
      .find(".container")
        .css({
          marginRight: (100 - 100 / frac) + "%",
          width: (100 / frac) + "%"
        })
  }
})</pre>
<p>The CSS is still about the same for the base functionality, but with the new stuff, it's far richer:</p>
<pre>.progress {
  display: inline-block;
  position: relative;
}
.progress .container {
  display: inline-block;
  position: relative;
  border: 1px solid #999;
  background-color: #f7f7f7;
  color: #f7f7f7;
  white-space: nowrap;
  padding: 0;
  margin: 0;
  width: 100%;
}
.progress .container .progress-bar {
  display: inline-block;
  position: absolute;
  z-index: 0;
  top: 0;
  left: 0;
  height: 100%;
  filter: alpha(opacity=80); /* yeah IE! */
  opacity: 0.8;
  /* .neutral is the default */
  background-color: #ddd;
  border-right: 1px solid #666;
}
.progress.overage.neutral-overage .container .progress-bar {
  background-color: #ddd;
  border-right: 1px solid #666;
}
.progress.ok .container .progress-bar,
.progress.overage.ok-overage .container .progress-bar {
  background-color: #dfd;
  border-right: 1px solid #393;
}
.progress.error .container .progress-bar,
.progress.overage.error-overage .container .progress-bar {
  background-color: #fdd;
  border-right-color: #c66;
}
.progress .container .status-text {
  display: inline-block;
  position: absolute;
  z-index: 1;
  top: 0;
  left: 0;
  color: #000;
  margin: 0;
  padding: 0;
}
.progress .container .status-text-spacer {
  margin: 0 5px;
}
</pre>
<p>So now instead of just doing this (which will render a neutral bar):</p>
<pre>&lt;span class="progress" rel="#raised / goal#"&gt;
  #dollarFormat(raised)# of #dollarFormat(goal)# raised...
&lt;/span&gt;
</pre>
<p>You can control the various presentation very granularly (red below 100% and green from 100% up):</p>
<pre>&lt;span class="progress <span style="color: #ff0000;">error ok-overage</span>" rel="#raised / goal#"&gt;
  #dollarFormat(raised)# of #dollarFormat(goal)# raised...
&lt;/span&gt;
</pre>
<p>The original version rendered green bars for everything, with this incarnation you can have neutral, ok, or error semantics for your bars.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/05/17/even-better-inline-progress-bars/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>cf.objective Presentation Links</title>
		<link>https://www.barneyb.com/barneyblog/2011/05/16/cf-objective-presentation-links/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/05/16/cf-objective-presentation-links/#comments</comments>
		<pubDate>Mon, 16 May 2011 19:58:03 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[coldfusion]]></category>
		<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1676</guid>
		<description><![CDATA[If you're looking for slides and/or code from any cf.objective presentations, they're mostly published on the cf.objective site.Â  Visit the schedule page, click on a session title, and there should be a download link beneath the session description.Â  There are still a few missing, but most are there, and the rest should be up in [...]]]></description>
			<content:encoded><![CDATA[<p>If you're looking for slides and/or code from any cf.objective presentations, they're mostly published on the cf.objective site.Â  Visit the <a href="http://www.cfobjective.com/index.cfm/schedule/">schedule page</a>, click on a session title, and there should be a download link beneath the session description.Â  There are still a few missing, but most are there, and the rest should be up in the near future now that the speakers are mostly all home.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/05/16/cf-objective-presentation-links/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>cf.objective: Replace Your Iron With a Cloud</title>
		<link>https://www.barneyb.com/barneyblog/2011/05/13/cf-objective-replace-your-iron-with-a-cloud/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/05/13/cf-objective-replace-your-iron-with-a-cloud/#comments</comments>
		<pubDate>Fri, 13 May 2011 19:50:01 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[development]]></category>
		<category><![CDATA[personal]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1671</guid>
		<description><![CDATA[My second talk from cf.objective 2011 is currently underway: Replace Your Iron With a Cloud.Â  Again, a slides-only presentation which you can get either as PPTX or PDF.
]]></description>
			<content:encoded><![CDATA[<p>My second talk from cf.objective 2011 is currently underway: Replace Your Iron With a Cloud.Â  Again, a slides-only presentation which you can get either as <a href="http://www.barneyb.com/barneyblog/wp-content/uploads/2011/05/slides1.pptx">PPTX</a> or <a href="http://www.barneyb.com/barneyblog/wp-content/uploads/2011/05/slides1.pdf">PDF</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/05/13/cf-objective-replace-your-iron-with-a-cloud/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>cf.objective: Progressive Enhancement</title>
		<link>https://www.barneyb.com/barneyblog/2011/05/12/cf-objective-progressive-enhancement/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/05/12/cf-objective-progressive-enhancement/#comments</comments>
		<pubDate>Thu, 12 May 2011 16:20:34 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[development]]></category>
		<category><![CDATA[personal]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1666</guid>
		<description><![CDATA[For those of you following along at home, I'm current presenting on Progressive Enhancement at cf.objective 2011.Â  This presentation is slides-only, and I've made them available as both PPTX and PDF if you want to take a look.
]]></description>
			<content:encoded><![CDATA[<p>For those of you following along at home, I'm current presenting on Progressive Enhancement at cf.objective 2011.Â  This presentation is slides-only, and I've made them available as both <a href="http://www.barneyb.com/barneyblog/wp-content/uploads/2011/05/slides.pptx">PPTX</a> and <a href="http://www.barneyb.com/barneyblog/wp-content/uploads/2011/05/slides.pdf">PDF</a> if you want to take a look.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/05/12/cf-objective-progressive-enhancement/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
