<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;DEUBQ3w5cSp7ImA9WxBSEEg.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175</id><updated>2009-12-17T08:24:12.229-05:00</updated><title>codeartisan</title><subtitle type="html">The Art of Writing Software</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://codeartisan.blogspot.com/" /><link rel="hub" href="http://pubsubhubbub.appspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>45</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/codeartisan" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><entry gd:etag="W/&quot;DkAMQ38_eyp7ImA9WxNUEEk.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-4696919759495493117</id><published>2009-10-31T21:06:00.005-04:00</published><updated>2009-10-31T23:06:22.143-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-31T23:06:22.143-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="riak" /><category scheme="http://www.blogger.com/atom/ns#" term="nosqleast" /><category scheme="http://www.blogger.com/atom/ns#" term="neo4j" /><category scheme="http://www.blogger.com/atom/ns#" term="key-value stores" /><category scheme="http://www.blogger.com/atom/ns#" term="voldemort" /><category scheme="http://www.blogger.com/atom/ns#" term="cassandra" /><category scheme="http://www.blogger.com/atom/ns#" term="pig" /><category scheme="http://www.blogger.com/atom/ns#" term="nosql" /><category scheme="http://www.blogger.com/atom/ns#" term="hbase" /><category scheme="http://www.blogger.com/atom/ns#" term="redis" /><category scheme="http://www.blogger.com/atom/ns#" term="cascading" /><category scheme="http://www.blogger.com/atom/ns#" term="hadoop" /><title>NoSQL East 2009 Redux</title><content type="html">I just attended &lt;a href="https://nosqleast.com/2009/"&gt;NoSQL East&lt;/a&gt; down it Atlanta over the last two days. This was a fantastic conference, well-organized, with not only great content and speakers, but also a very well-educated audience. There was a telling moment in the first non-keynote talk where the speaker asked the audience "How many people have read the &lt;a href="http://s3.amazonaws.com/AllThingsDistributed/sosp/amazon-dynamo-sosp2007.pdf"&gt;Dynamo paper&lt;/a&gt;?" and easily 95% of the audience put their hands up.&lt;br /&gt;&lt;br /&gt;I'd divide the major focus areas into the following groups:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;key-value stores (&lt;a href="http://riak.basho.com/"&gt;Riak&lt;/a&gt;, &lt;a href="http://project-voldemort.com/"&gt;Voldemort&lt;/a&gt;, &lt;a href="http://incubator.apache.org/cassandra/"&gt;Cassandra&lt;/a&gt;)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;massively parallel data mining (&lt;a href="http://hadoop.apache.org/pig/"&gt;Pig&lt;/a&gt;, &lt;a href="http://www.cascading.org/"&gt;Cascading&lt;/a&gt;)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;column-oriented datastores (Cassandra, &lt;a href="http://hadoop.apache.org/hbase/"&gt;HBase&lt;/a&gt;)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;document collection databases (&lt;a href="http://couchdb.apache.org/"&gt;CouchDB&lt;/a&gt;, &lt;a href="http://www.mongodb.org/display/DOCS/Home"&gt;MongoDB&lt;/a&gt;)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;stuff I couldn't make up my mind about (&lt;a href="http://neo4j.org/"&gt;Neo4J&lt;/a&gt;, &lt;a href="http://code.google.com/p/redis/"&gt;Redis&lt;/a&gt;, &lt;a href="http://www.slideshare.net/timanglade/tin"&gt;Tin&lt;/a&gt;)&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;As one of the speakers put it, which one you need depends on the shape of your data; these are all isomorphic in one way or another to each other, just as Turing complete languages are all essentially complete. But you still want to choose the right tool for the job. One nice touch was that a number of the systems were introduced not by one of the project committers, but rather by people who were using them to get stuff done. It was a very practical focus, much appreciated.&lt;br /&gt;&lt;br /&gt;There were a couple of interesting tidbits from the conference, including a rollicking, confusing talk by &lt;a href="http://en.wikipedia.org/wiki/John_Day_%28computer_scientist%29"&gt;John "My first network address was '12'" Day&lt;/a&gt; about network architecture and how TCP/IP had really gotten it all wrong. This guy was about 3 planes of existence above what I could follow at 9:15am--I had a distinct feeling he knew exactly what he was talking about, and was probably right, but there was no way he was making sense even to a very technical audience. Then there was the poor guy from Microsoft Research who had done some interesting distributed IDE work...for the .NET framework. In a sea of Mac and Linux laptops across the audience, he had a bunch of people who really couldn't make practical use of what he'd done.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Key-Value Stores&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;I've got to give a pretty big nod to Riak here for a straight-up key-value store; &lt;a href="http://twitter.com/justinsheehy"&gt;Justin Sheehy&lt;/a&gt; basically gave a talk about decentralized, scalable, lights-out system design that might well have been a good keynote, and it was clear that Riak was designed with high priority on those aspects. Major advantages over Voldemort in my mind are: ability to add/subtract nodes from running clusters, tunable (per-bucket, per-request!) quorum parameters (N,R,W), plus support for pluggable consistent hashing modules, partition distribution modules, and conflict resolution modules. For most of my use cases, with N=3, I'd probably do R=1,W=3 to optimize for read latency, as most of our writes would either be a user clicking "save" or would be the result of an asynchronous background process. On the other hand, if I wanted to build something like &lt;a href="http://aws.amazon.com/sqs/"&gt;SQS&lt;/a&gt; on top of this (which I conjecture is possible), I'd probably do W=1,R=3 to optimize for the "post a message" latency.&lt;br /&gt;&lt;br /&gt;While I was there, I had a great talk with them where we went over how there were going to expose the conflict resolution out up to the client via their HTTP/JSON interface. There's no out-of-the-box support for multiple datacenter awareness, although it seemed possible to add it via the pluggable partition distribution module, although their inter-node communication leverages &lt;a href="http://www.erlang.org/"&gt;Erlang&lt;/a&gt;'s native networking, meaning some kind of VPN/IPSec tunnel would be the main way to make this work across sites. I'm pretty sure no matter what you would want to use in this space you'll probably have to end up using a VPN over WAN to give the appearance of a seamless LAN cluster (although the Cassandra guys piped up in the conference IRC channel that Cassandra has multi-site support already).&lt;br /&gt;&lt;br /&gt;I can't tell whether being written in Erlang is a plus or a minus. On the one hand, it won't plug in well to our JMX-based monitoring frameworks, and I'm pretty sure very few folks within our organization have ever built or deployed an Erlang system. On the other hand, Erlang is the perfect choice for highly concurrent, fault-tolerant programming, and would probably be right up the alley of several of our programmers (I'm proud to say that while we are primarily a Java shop, we have a lot of other language proficiencies in-house spanning Ruby, Python, Clojure, and Scheme. Based on my brief escapade with it last night, I'm confident that (1) we have a number of folks who could pick it up easily and (2) who would jump at the chance). This is probably a wash.&lt;br /&gt;&lt;br /&gt;Very interesting in my mind is that some of the key-value stores have brought over some features from other design spaces; Riak allows some basic link structure in their values, including a version of targeted &lt;a href="http://en.wikipedia.org/wiki/MapReduce"&gt;MapReduce&lt;/a&gt; that can follow links, which starts to make it feel like a graph-oriented database like Neo4j. Similarly, Cassandra has support for column-oriented indices like HBase does. It's clear there's probably a project out there to scratch your particular itch.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Massive-scale data mining&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;We saw a couple of talks from folks who were using &lt;a href="http://hadoop.apache.org/"&gt;Hadoop&lt;/a&gt; for analyzing very large data sets. But furthermore, they were using frameworks like Pig and Cascading on top of Hadoop to do a lot of ad-hoc queries. &lt;a href="http://twitter.com/"&gt;Twitter&lt;/a&gt; in fact uses Pig to do all of their interesting web analytics, and they've taken all their analytics BAs who are used to doing ad-hoc SQL queries and trained them up with little problem in Pig. This is probably somewhere on our horizon, although there are larger cats to skin at the moment.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Column-oriented datastores&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Cassandra and HBase were the major players here. &lt;a href="http://digg.com/"&gt;Digg&lt;/a&gt; is moving all of their backend storage over to Cassandra after some successful pilots, and we saw a great talk by &lt;a href="http://twitter.com/markgunnels"&gt;Mark Gunnels&lt;/a&gt; about how he is using HBase because it makes it "simple to get sh*t done". Apparently recent releases of HBase have made strides in fault tolerance (there is now an election algorithm for some of the special name nodes) and latency (apparently performance optimization was a major focus of the 0.20 release). There's an interesting article that describes some wide-area replication schemes that are available with HBase that sound intriguing (although once you are beyond two datacenters, I am convinced that you are better off with the VPN LAN solution if you want to have any hope of achieving eventual consistency).&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Document-oriented databases&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;While the column-oriented datastores are good at organizing semi-structured data, the document-oriented guys are really all about organizing largely unstructured documents, and focus on doing some wild ad-hoc MapReduce queries. I still need to be convinced about the scaling, replication, and geographic distribution capabilities in this space, so it may be a while before we dip our toes in here.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Other&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;There was a very interesting talk about Tin ("the database so tiny they had to shorten its name"). This basically seemed to be a very clever approach for delivering stock data that leveraged a basic filesystem with range queries and rewriting rules via &lt;a href="http://www.sinatrarb.com/"&gt;Sinatra&lt;/a&gt;. Turns out web servers serving static files goes pretty fast, if that's a suitable representation for your stuff (seems like his primary use case is for delivering read-only out to clients, where the data gets updated asynchronously in the background by the system). We actually have some datasets that are like that, so this is intriguing!&lt;br /&gt;&lt;br /&gt;There was a talk from Neo4j, a graph-oriented database, which I just can't wrap my head around yet. I think I need to read up on some of the background research papers in this area. Certainly, the notion of a datastore based around the notion of link relations would likely be easy to expose as a REST HTTP interface, which is attractive. Our particular domain model can actually be nicely normalized, however (we are currently running off a traditional RDBMS after all), so I'm not sure we need the full semantic flexibility this offers.&lt;br /&gt;&lt;br /&gt;There was also a talk from Redis; this is primarily an in-memory storage system with blazing-fast speeds, and the ability to write out to disk is really an afterthought. During the talk he showed a screen snapshot of a "top" running on a cloud-hosted virtual node with 60GB of memory. I cannot make up my mind whether this is ultimately just a badass memcached, or if he's on the cusp of something: if you have enough memory to hold your whole dataset, and you are sufficiently fault-tolerant, why bother writing to disk at all? Especially if you can easily get a &lt;em&gt;periodic&lt;/em&gt; snapshot out to disk in the background that can be properly backed up for disaster recovery.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;As folks pointed out, "NoSQL" might better be written "NOSQL" to mean "Not Only SQL". I didn't sense a lot of MySQL hatred in here; quite the contrary, many people were very complementary that there were certain things it does *really* well, and that the MySQL hammer was something that was staying firmly in their toolboxes. However, it is clear that there is a maturing community of very practically-minded folks that are looking for a new set of tools to drive their particular screws. Although to be sure (and this is something that I think some of the conference tweets corroborated), this also implies we all have a screw loose or two....&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-4696919759495493117?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/vnhD8LXWgOiprubL-LdZXG7NCF4/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/vnhD8LXWgOiprubL-LdZXG7NCF4/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/vnhD8LXWgOiprubL-LdZXG7NCF4/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/vnhD8LXWgOiprubL-LdZXG7NCF4/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/4696919759495493117/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=4696919759495493117" title="10 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/4696919759495493117?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/4696919759495493117?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/fh8mTSXTrMY/nosql-east-2009-redux.html" title="NoSQL East 2009 Redux" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">10</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2009/10/nosql-east-2009-redux.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0IHRHs9fyp7ImA9WxNVGU8.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-2728493035425647082</id><published>2009-10-30T11:53:00.004-04:00</published><updated>2009-10-30T13:58:55.567-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-30T13:58:55.567-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="erlang" /><category scheme="http://www.blogger.com/atom/ns#" term="message passing" /><category scheme="http://www.blogger.com/atom/ns#" term="oop" /><category scheme="http://www.blogger.com/atom/ns#" term="concurrency" /><title>Object Calls == Message Passing in Erlang</title><content type="html">I started playing around with &lt;a title="Official site for the Erlang programming language" href="http://www.erlang.org/"&gt;Erlang&lt;/a&gt; last night as a result of learning about &lt;a title="home page for Basho Technologies, Inc." href="http://www.basho.com/"&gt;Basho&lt;/a&gt;'s key-value store &lt;a title="home page for the Riak distributed key-value store" href="http://riak.basho.com/"&gt;Riak&lt;/a&gt; at &lt;a title="2009 conference about non-relational data storage" href="https://nosqleast.com/2009/"&gt;NoSQL East&lt;/a&gt; yesterday (more specifically, it was due to &lt;a title="Justin Sheehy's twitter profile" href="http://twitter.com/justinsheehy"&gt;Justin Sheehy&lt;/a&gt;'s talk, and two realizations: (1) this guys *gets* a lot of important *operational* design choices in this space, and (2) he decided to build his system in Erlang).&lt;br /&gt;&lt;br /&gt;So I decided to read the &lt;a href="http://www.erlang.org/course/course.html"&gt;online Erlang "course"&lt;/a&gt; and started working on the &lt;a title="concurrency exercises for the online Erlang course" href="http://www.erlang.org/course/exercises.html#conc"&gt;exercises&lt;/a&gt;. One of them was:&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;Write a function which starts N processes in a ring, and sends a message M times around all the processes in the ring. After the messages have been sent the processes should terminate gracefully.&lt;br /&gt;&lt;img alt="picture of a unidirectional ring of nodes" src="http://www.erlang.org/course/ex2.gif"/&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;And so, summoning vaguely-remembered lectures by &lt;a title="Bob Harper's home page at Carnegie Mellon University" href="http://www.cs.cmu.edu/~rwh/"&gt;Bob Harper&lt;/a&gt; in "Fundamentals of Computer Science II" at CMU on doing object-oriented programming in &lt;a href="http://en.wikipedia.org/wiki/Scheme_%28programming_language%29"&gt;Scheme&lt;/a&gt;, and remembering that the original &lt;a href="http://www.smalltalk.org/smalltalk/whatissmalltalk.html"&gt;Smalltalk&lt;/a&gt; OOP guys always said "send an object a message" rather than "invoke a method on an object", I set to work. [Editor's note: please feel free to post comments showing me better ways, I have known Erlang for all of about 12 hours at this point!]&lt;br /&gt;&lt;br /&gt;Let's get the declarations out of the way. I need to define the entry point function which creates the ring of N nodes and sends the message around it M times, and I know I'm going to need a function representing a node in the ring, since I'm going to have to spawn processes for them.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(ring).&lt;br /&gt;-export([ring_msg/3, ring_node/1]).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Ok, what job does a node in the ring have? Well, most of the time, when it receives a message, it just needs to pass it on to the next guy. So my node process is going to need to know about its next neighbor. Now in Erlang, what I would normally think of as an object can be modelled as a recursive function that passes its current state back into itself as an argument, and processes "method calls" by receiving messages. Interestingly, not all method calls actually have to send something back to the caller!&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;ring_node(Next) -&gt;&lt;br /&gt;  receive&lt;br /&gt;    { pass, M, Msg } -&gt;&lt;br /&gt;      % Note that we got this message. &lt;br /&gt;      io:format("Node ~w~n",[Msg]),&lt;br /&gt;      % Pass the message on around the ring.&lt;br /&gt;      Next ! { pass, M, Msg },&lt;br /&gt;      % If the count was down to zero, I can&lt;br /&gt;      % exit, otherwise, I loop and wait for&lt;br /&gt;      % the next incoming message.&lt;br /&gt;      if M == 0 -&gt; ok;&lt;br /&gt;         true -&gt; ring_node(Next)&lt;br /&gt;      end&lt;br /&gt;  end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ok, seems pretty straightforward. But if I had a ring of these set up, a message would just keep running around the ring. At least one node needs to be special, so that it can decrement the count M as the message comes through. It's pretty similar to the ring_node above, but is a little different.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;init_node(Next) -&gt; receive&lt;br /&gt;    % message has been all the way around&lt;br /&gt;    % the last time, so I can quit&lt;br /&gt;    { pass, 0, _ } -&gt; ok;&lt;br /&gt;    % otherwise, log the message and pass&lt;br /&gt;    % it on, decrementing the count&lt;br /&gt;    { pass, M, Msg } -&gt;&lt;br /&gt;      io:format("Node ~w~n",[Msg]),&lt;br /&gt;      Next ! { pass, M-1, Msg },&lt;br /&gt;      init_node(Next)&lt;br /&gt;  end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now an interesting thing here is that the init_node and the ring_node can both handle the "pass" message, and that when they send the message on, they don't actually care &lt;span style="font-style:italic;"&gt;what&lt;/span&gt; their "Next" process is. It's like both of these "objects" implement the following interface:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public interface MessagePasser {&lt;br /&gt;  void pass(int count, Object msg);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ok, so now if we can create a ring with 1 init_node and (N-1) ring_nodes, we're all set if we inject the initial Msg into the init_node. So let's think about constructing a ring of nodes; if we have a node handy, we can pass that in as the initial argument (think "constructor") to a ring_node process to use as its Next node, then we just count down:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;ring(Last, 0) -&gt; Last;&lt;br /&gt;ring(Last, N) -&gt; &lt;br /&gt;  RN = spawn(ring, ring_node, [Last]),&lt;br /&gt;  ring(RN, N-1).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Hmm, that's close, but that's a linked-list of nodes, not a ring. But we can't pass a node in as a constructor argument to the first node we create, because we don't have any yet! So it seems like we'll need to construct a linked-list of nodes, and then "close the loop" by stitching the front and the back together. Our init_node is already a special node, so maybe we can extend it this way:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;init_node(Next) -&gt; receive&lt;br /&gt;    % acknowledge the request, update state&lt;br /&gt;    { setNext, N, From } -&gt; From ! ok, init_node(N);&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In other words, the init_node can get a special message telling it to "update" its Next state. In some sense, we've just done this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public interface InitNode extends MessagePasser {&lt;br /&gt;  void setNext(MessagePasser N);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We want to acknowledge the request so our ring construction knows when that message has been processed -- we don't want to hand the ring back until it's all stitched together, and we can't guarantee ordering of message delivery unless we specifically wait for a response. So here's the full ring construction:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;ring(N) when is_integer(N) -&gt;&lt;br /&gt;  % just pass in a placeholder for Next&lt;br /&gt;  RN0 = spawn(ring, init_node, [nil]),&lt;br /&gt;  ring(RN0, RN0, N-1);&lt;br /&gt;% finished stitching, can return our&lt;br /&gt;% init node to the caller&lt;br /&gt;ring(Init) -&gt; receive ok -&gt; Init end.&lt;br /&gt;ring(Init, Last, 0) -&gt; Init ! { setNext, Last, self()}, ring(Init);&lt;br /&gt;ring(Init, Last, N) -&gt;&lt;br /&gt;  RN = spawn(ring, ring_node, [Last]),&lt;br /&gt;  ring(Init, RN, N-1).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Finally, the thing we're trying to do (including optimizing the degenerate cases):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;ring_msg(0, _, _) -&gt; ok;&lt;br /&gt;ring_msg(_, 0, _) -&gt; ok;&lt;br /&gt;ring_msg(N, M, Msg) -&gt;&lt;br /&gt;  Init = ring(N), Init ! { pass, M, Msg }, ok.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Actually runs, too! It's pretty neat to see polymorphism via being able to accept the same message, and I've always loved the pattern matching in &lt;a href="http://en.wikipedia.org/wiki/ML_%28programming_language%29"&gt;ML&lt;/a&gt; (both &lt;a href="http://www.smlnj.org/"&gt;SML&lt;/a&gt; and &lt;a href="http://caml.inria.fr/ocaml/"&gt;OCaml&lt;/a&gt; variants!). Some pretty serious systems programs are getting written in this language; it's clear that the process spawning methodology lends itself well to a &lt;a href="http://www.eecs.harvard.edu/~mdw/proj/seda/"&gt;SEDA&lt;/a&gt;-style approach which is great for graceful degradation of service, and the fully-functional style (no mutation) means that you have &lt;span style="font-style:italic;"&gt;no locks&lt;/span&gt;, &lt;span style="font-style:italic;"&gt;no shared state&lt;/span&gt;, and hence safe concurrency (as long as you can model what you're doing properly).&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-2728493035425647082?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/2jWyuE_a2KP8zB2vO96UZ7JYzuE/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/2jWyuE_a2KP8zB2vO96UZ7JYzuE/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/2jWyuE_a2KP8zB2vO96UZ7JYzuE/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/2jWyuE_a2KP8zB2vO96UZ7JYzuE/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/2728493035425647082/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=2728493035425647082" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/2728493035425647082?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/2728493035425647082?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/nazHZT-wEBE/object-calls-message-passing-in-erlang.html" title="Object Calls == Message Passing in Erlang" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2009/10/object-calls-message-passing-in-erlang.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CE4CSH4_cSp7ImA9WxNSE0k.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-5871308384921062660</id><published>2009-08-26T21:54:00.004-04:00</published><updated>2009-08-26T23:16:09.049-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-08-26T23:16:09.049-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="agile" /><category scheme="http://www.blogger.com/atom/ns#" term="architecture" /><title>Agile Architecture Anti-Patterns</title><content type="html">I have been the solution architect for an enterprise-spanning project which is now getting geared up for its next phase. This project touches more than 10 different development teams across at least three company divisions and involves at least one external ISV. So, you know, a pretty small project.&lt;br /&gt;&lt;br /&gt;As I reflect on the process of hashing out all the technical details, I realized I got stretched pretty thin. I really didn't have time to write much down in any formal fashion this time around, due to the time pressure on the project and the number of other things I was also trying to juggle. I have instead been spending a lot of time drawing stuff on whiteboards for various technical audiences. The things I did have time to write up were only the most general concepts and patterns - i.e. the really high-level architecture.&lt;br /&gt;&lt;br /&gt;At least part of the reason I didn't have time to write anything else down was that I was spending all my time in technical design discussions with all the various groups, hashing out nitty-gritty integration details and then bringing full integration designs back to the development groups to run with. The last time through, this worked out well, because the developers in my home organization are used to agile development and being responsible for their own technical design, so they were able to take my whiteboard sketches, continue to consult with me, and produce solid, working code. It also worked out ok because the last phase was actually the first phase of this project, and I had enough lead time to work out all the details ahead of time.&lt;br /&gt;&lt;br /&gt;This time around, the time pressure is pretty high, plus the work-ahead time I would have had in a traditional "up-front" architecture process was actually consumed by helping to get the first phase out the door. So I find I actually don't have enough time to do the same detailed design that I did the first time around before the development teams are scheduled to start. This would seem to be a major conundrum.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Antipattern 1:&lt;/span&gt; waterfall, up-front architecture doesn't pipeline well if the architecture phase extends concurrently all the way through the development phase.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Corollary:&lt;/span&gt; trying to waterfall development and QA doesn't pipeline well either for exactly the same reason (because developers have to keep working their code to fix bugs).&lt;br /&gt;&lt;br /&gt;The other anti-pattern that I was running into was with development teams in some of the other divisions; I would work with the enterprise architects to hash out a pretty detailed design, but when we brought that to the development teams, there was a ton of convincing, resistance, and redesign that ended up happening. Now, I'm not sure exactly which anti-pattern we were running into, but it was one of the following:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Antipattern 2:&lt;/span&gt; up-front architecture doesn't work if the architects aren't familiar enough with the details of the systems/teams they are designing for, because the designs won't "fit right".&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Antipattern 3:&lt;/span&gt; up-front architecture doesn't work if the architects haven't been able to build up sufficient technical cred to sell their designs to the developers.&lt;br /&gt;&lt;br /&gt;For sure both of those applied to me with respect to the developers and technical leads of the groups in the other divisions.&lt;br /&gt;&lt;br /&gt;I've been thinking a lot recently about the right way to approach this, and really enjoyed this &lt;a href="http://www.agiledata.org/essays/enterpriseArchitecture.html" title="read more about an approach to agile enterprise architecture"&gt;essay on Agile Enterprise Architecture&lt;/a&gt; by Scott W. Adler. He proposes a much more lightweight, hands-on approach to architecture which can best be described as guiding the agile development teams to develop the right architecture themselves.&lt;br /&gt;&lt;br /&gt;Fred Brooks writes in &lt;span style="font-style:italic;"&gt;&lt;a href="http://www.amazon.com/Mythical-Man-Month-Software-Engineering-Anniversary/dp/0201835959" title="link to an Amazon listing for the Mythical Man-Month book about software engineering"&gt;The Mythical Man-Month&lt;/a&gt;&lt;/span&gt; that a systems' architecture must proceed from one or a small number of minds in order to be coherent (I can't find a copy right now, so that's paraphrased). At the same time, agile development techniques like &lt;a href="http://www.extremeprogramming.org/" title="introduction to the Extreme Programming software development methodology"&gt;Extreme Programming (XP)&lt;/a&gt; or &lt;a href="http://www.controlchaos.com/" title="home page of the Scrum software development framework"&gt;Scrum&lt;/a&gt; suggest that the development teams ought to be responsible for evolving their architecture organically. I'm starting to think that both of these are true, and that they are not mutually exclusive, and it is related to another realization I've had (but that I have a hard time remembering):&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;A solution architect is responsible for seeing that the end-to-end solution hangs together technically. It is not necessary for the architect to produce the whole end-to-end design himself to achieve this.&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;Going forward, I think I'm going to take the following approach: figure out broadly which development teams are going to need to interact and what their coarse responsibilities are, then immediately get them involved on working out the solution. In true agile fashion, let them work out the details, and just play Product Owner to set high-level (technical) requirements, being on-hand to participate in the design discussions. I think this leverages best the available technical design talent in the development teams, while making sure that someone is tracking it all and fitting it into a coherent mental model to make Mr. Brooks happy.&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-5871308384921062660?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/WDvAm-74F3_9CNFwpD---jFdeRo/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/WDvAm-74F3_9CNFwpD---jFdeRo/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/WDvAm-74F3_9CNFwpD---jFdeRo/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/WDvAm-74F3_9CNFwpD---jFdeRo/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/5871308384921062660/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=5871308384921062660" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/5871308384921062660?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/5871308384921062660?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/ylaLmMxI8so/agile-architecture-anti-patterns.html" title="Agile Architecture Anti-Patterns" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2009/08/agile-architecture-anti-patterns.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEMGSXs9fCp7ImA9WxNSEkk.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-1783857594132741222</id><published>2009-05-15T16:18:00.006-04:00</published><updated>2009-08-25T20:27:08.564-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-08-25T20:27:08.564-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="rsa" /><category scheme="http://www.blogger.com/atom/ns#" term="jce" /><category scheme="http://www.blogger.com/atom/ns#" term="openssl" /><category scheme="http://www.blogger.com/atom/ns#" term="public key cryptography" /><title>RSA Public Key Cryptography in Java</title><content type="html">&lt;a href="http://en.wikipedia.org/wiki/Public-key_cryptography" title="wikipedia article about public key cryptography"&gt;Public key cryptography&lt;/a&gt; is a well-known concept, but for some reason the &lt;a href="http://java.sun.com/javase/technologies/security/" title="home page for Sun's Java standard edition security products"&gt;JCE (Java Cryptography Extensions)&lt;/a&gt; &lt;a href="http://java.sun.com/j2se/1.4.2/docs/guide/security/jce/JCERefGuide.html" title="reference guide for Sun's Java Cryptography Extensions"&gt;documentation&lt;/a&gt; doesn't at all make it clear how to interoperate with common public key formats such as those produced by &lt;a href="http://www.openssl.org/" title="openssl home page"&gt;&lt;tt&gt;openssl&lt;/tt&gt;&lt;/a&gt;. If you try to do a search on the web for how to make &lt;a href="http://en.wikipedia.org/wiki/RSA" title="wikipedia article about RSA cryptography schemes"&gt;RSA&lt;/a&gt; public key cryptography work in Java, you quickly find a lot of people asking questions and not a lot of people answering them. In this post, I'm going to try to lay out very clearly how I got this working.&lt;br /&gt;&lt;br /&gt;Just to set expectations, this is not a tutorial about how to &lt;em&gt;use&lt;/em&gt; the cryptography APIs themselves in &lt;a href="http://java.sun.com/j2se/1.5.0/docs/api/javax/crypto/package-frame.html" title="javadocs for the Java cryptography API"&gt;&lt;tt&gt;javax.crypto&lt;/tt&gt;&lt;/a&gt; (look at the JCE tutorials from Sun for this); nor is this a primer about how public key cryptography works. This article is really about how to manage the keys with off-the-shelf utilities available to your friendly, neighborhood sysadmin and still make use of them from Java programs. Really, this boils down to "how do I get these darn keys loaded into a Java program where they can be used?" This is the article I wish I had when I started trying to muck around with this stuff....&lt;br /&gt;&lt;br /&gt;&lt;h2 style="text-align: left;"&gt;Managing the keys&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Openssl.&lt;/b&gt; This is the de-facto tool sysadmins use for managing public/private keys, &lt;a href="http://en.wikipedia.org/wiki/X.509" title="wikipedia article about the X.509 certificate standard"&gt;X.509 certificates&lt;/a&gt;, etc. This is what we want to create/manage our keys with, so that they can be stored in formats that are common across most Un*x systems and utilities (like, say, C programs using the &lt;tt&gt;openssl&lt;/tt&gt; library...). Java has this notion of its own keystore, and Sun will give you the &lt;a href="http://java.sun.com/j2se/1.3/docs/tooldocs/win32/keytool.html" title="man page for the keytool utility"&gt;keytool command&lt;/a&gt; with Java, but that doesn't do you much good outside of Java world.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Creating the keypair.&lt;/b&gt; We are going to create a keypair, saving it in openssl's preferred PEM format. PEM formats are ASCII and hence easy to email around as needed. However, we will need to save the keys in the binary DER format so Java can read them. Without further ado, here is the magical incantation for creating the keys we'll use:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;# generate a 2048-bit RSA private key&lt;br /&gt;$ openssl genrsa -out private_key.pem 2048&lt;br /&gt;&lt;br /&gt;# convert private Key to PKCS#8 format (so Java can read it)&lt;br /&gt;$ openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key.pem \&lt;br /&gt;    -out private_key.der -nocrypt&lt;br /&gt;&lt;br /&gt;# output public key portion in DER format (so Java can read it)&lt;br /&gt;$ openssl rsa -in private_key.pem -pubout -outform DER -out public_key.der&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;You keep &lt;tt&gt;private_key.pem&lt;/tt&gt; around for reference, but you hand the DER versions to your Java programs.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2 style="text-align: left;"&gt;Loading the keys into Java&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Really, this boils down to knowing what type of KeySpec to use when reading in the keys. To read in the private key:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import java.io.*;&lt;br /&gt;import java.security.*;&lt;br /&gt;import java.security.spec.*;&lt;br /&gt;&lt;br /&gt;public class PrivateKeyReader {&lt;br /&gt;&lt;br /&gt;  public static PrivateKey get(String filename)&lt;br /&gt;    throws Exception {&lt;br /&gt;    &lt;br /&gt;    File f = new File(filename);&lt;br /&gt;    FileInputStream fis = new FileInputStream(f);&lt;br /&gt;    DataInputStream dis = new DataInputStream(fis);&lt;br /&gt;    byte[] keyBytes = new byte[(int)f.length()];&lt;br /&gt;    dis.readFully(keyBytes);&lt;br /&gt;    dis.close();&lt;br /&gt;&lt;br /&gt;    PKCS8EncodedKeySpec spec =&lt;br /&gt;      new PKCS8EncodedKeySpec(keyBytes);&lt;br /&gt;    KeyFactory kf = KeyFactory.getInstance("RSA");&lt;br /&gt;    return kf.generatePrivate(spec);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And now, to read in the public key:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import java.io.*;&lt;br /&gt;import java.security.*;&lt;br /&gt;import java.security.spec.*;&lt;br /&gt;&lt;br /&gt;public class PublicKeyReader {&lt;br /&gt;&lt;br /&gt;  public static PublicKey get(String filename)&lt;br /&gt;    throws Exception {&lt;br /&gt;    &lt;br /&gt;    File f = new File(filename);&lt;br /&gt;    FileInputStream fis = new FileInputStream(f);&lt;br /&gt;    DataInputStream dis = new DataInputStream(fis);&lt;br /&gt;    byte[] keyBytes = new byte[(int)f.length()];&lt;br /&gt;    dis.readFully(keyBytes);&lt;br /&gt;    dis.close();&lt;br /&gt;&lt;br /&gt;    X509EncodedKeySpec spec =&lt;br /&gt;      new X509EncodedKeySpec(keyBytes);&lt;br /&gt;    KeyFactory kf = KeyFactory.getInstance("RSA");&lt;br /&gt;    return kf.generatePublic(spec);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That's about it. The hard part was figuring out a compatible set of:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;openssl DER output options (particularly the &lt;a href="http://en.wikipedia.org/wiki/PKCS" title="wikipedia article about the PKCS public key crytopgrahy standards"&gt;PKCS#8 encoding&lt;/a&gt;)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;which type of KeySpec Java needed to use (strangely enough, the public key needs the "X509" keyspec, even though you would normally handle X.509 certificates with&lt;br /&gt;the &lt;a href="http://www.openssl.org/docs/apps/x509.html" title="Unix man page for the openssl x509 command"&gt;&lt;tt&gt;openssl x509&lt;/tt&gt;&lt;/a&gt; command, not the &lt;a href="http://www.openssl.org/docs/apps/rsa.html" title="Unix man page for the openssl rsa command"&gt;&lt;tt&gt;openssl rsa&lt;/tt&gt;&lt;/a&gt; command. Real intuitive.)&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;From here, signing and verifying work as described in the JCE documentation; the only other thing you need to know is that you can use the "SHA1withRSA" algorithm when you get your &lt;a href="http://java.sun.com/j2se/1.5.0/docs/api/java/security/Signature.html" title="javadocs for the Signature class"&gt;&lt;tt&gt;java.security.Signature&lt;/tt&gt;&lt;/a&gt; instance for signing/verifying, and that you want the "RSA" algorithm when you get your &lt;a href="http://java.sun.com/j2se/1.5.0/docs/api/javax/crypto/Cipher.html" title="javadocs for the Cipher class"&gt;&lt;tt&gt;javax.crypto.Cipher&lt;/tt&gt;&lt;/a&gt; instance for encrypting/decrypting.&lt;br /&gt;&lt;br /&gt;Many happy security returns to you.&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-1783857594132741222?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/WCDJsbZhhQcgFvsOgUWPJVI-oWY/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/WCDJsbZhhQcgFvsOgUWPJVI-oWY/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/WCDJsbZhhQcgFvsOgUWPJVI-oWY/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/WCDJsbZhhQcgFvsOgUWPJVI-oWY/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/1783857594132741222/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=1783857594132741222" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/1783857594132741222?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/1783857594132741222?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/FMBjix_zRWA/public-key-cryptography-in-java.html" title="RSA Public Key Cryptography in Java" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">6</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2009/05/public-key-cryptography-in-java.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0YGQHw4eSp7ImA9WxVXE00.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-8142653959254891303</id><published>2009-02-10T18:26:00.005-05:00</published><updated>2009-02-10T18:38:41.231-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-10T18:38:41.231-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="csv" /><category scheme="http://www.blogger.com/atom/ns#" term="atom" /><category scheme="http://www.blogger.com/atom/ns#" term="blogger" /><category scheme="http://www.blogger.com/atom/ns#" term="archives" /><category scheme="http://www.blogger.com/atom/ns#" term="download" /><category scheme="http://www.blogger.com/atom/ns#" term="backup" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><title>Downloading your Blogger archives</title><content type="html">A &lt;a href="http://chewyourgrouse.blogspot.com/"&gt;friend&lt;/a&gt; was looking for a way to grab an archive of his Blogger posts into a CSV file he could do text mining on (and presumably, for a low-fi backup mechanism).&lt;br /&gt;&lt;br /&gt;I wrote this Python script for him, enjoy.&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;#!/usr/bin/env python&lt;br /&gt;#&lt;br /&gt;# Copyright (C) 2009 by Jon Moore&lt;br /&gt;#&lt;br /&gt;# This program is free software: you can redistribute it and/or modify&lt;br /&gt;# it under the terms of the GNU General Public License as published by&lt;br /&gt;# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;# (at your option) any later version.&lt;br /&gt;#&lt;br /&gt;# This program is distributed in the hope that it will be useful,&lt;br /&gt;# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;# GNU General Public License for more details.&lt;br /&gt;#&lt;br /&gt;# You should have received a copy of the GNU General Public License&lt;br /&gt;# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&gt;.&lt;br /&gt;&lt;br /&gt;import csv&lt;br /&gt;import urllib2&lt;br /&gt;import unicodedata&lt;br /&gt;import xml.etree.ElementTree as etree&lt;br /&gt;&lt;br /&gt;blog_feed = 'http://codeartisan.blogspot.com/feeds/posts/default'&lt;br /&gt;output = 'posts.csv'&lt;br /&gt;&lt;br /&gt;ATOM_NS = 'http://www.w3.org/2005/Atom'&lt;br /&gt;&lt;br /&gt;def norm(s):&lt;br /&gt;    if not s: return None&lt;br /&gt;    return s.encode('ascii','ignore')&lt;br /&gt;&lt;br /&gt;def main():&lt;br /&gt;    f = open(output, 'wb')&lt;br /&gt;    csv_wr = csv.writer(f)&lt;br /&gt;    url = blog_feed + '?max-results=100'&lt;br /&gt;    csv_wr.writerow(['id','published','updated','permalink','title','content'])&lt;br /&gt;    while url:&lt;br /&gt;        print "fetching", url&lt;br /&gt;        feed = etree.fromstring(urllib2.urlopen(url).read())&lt;br /&gt;        for entry in feed.findall("{%s}entry" % ATOM_NS):&lt;br /&gt;            id = entry.find("{%s}id" % ATOM_NS).text&lt;br /&gt;            published = entry.find("{%s}published" % ATOM_NS).text&lt;br /&gt;            updated = entry.find("{%s}updated" % ATOM_NS).text&lt;br /&gt;            title = norm(entry.find("{%s}title" % ATOM_NS).text)&lt;br /&gt;            content = norm(entry.find("{%s}content" % ATOM_NS).text)&lt;br /&gt;            perm_url = ''&lt;br /&gt;            for link in entry.findall("{%s}link" % ATOM_NS):&lt;br /&gt;                if (link.get('rel') == 'alternate'&lt;br /&gt;                    and link.get('type') == 'text/html'):&lt;br /&gt;                    perm_url = link.get('href')&lt;br /&gt;                    break&lt;br /&gt;            csv_wr.writerow([id,published,updated,perm_url,title,content])&lt;br /&gt;            print "wrote",id&lt;br /&gt;        url = None&lt;br /&gt;        for link in feed.findall("{%s}link" % ATOM_NS):&lt;br /&gt;            if link.get('rel') == 'next':&lt;br /&gt;                url = link.get('href')&lt;br /&gt;                break&lt;br /&gt;    f.close()&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;    main()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-8142653959254891303?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/0NmvsZ4a7y-Q2_NB9aS3AYNrZ3I/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/0NmvsZ4a7y-Q2_NB9aS3AYNrZ3I/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/0NmvsZ4a7y-Q2_NB9aS3AYNrZ3I/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/0NmvsZ4a7y-Q2_NB9aS3AYNrZ3I/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/8142653959254891303/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=8142653959254891303" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/8142653959254891303?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/8142653959254891303?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/tm8juVttW2A/downloading-your-blogger-archives.html" title="Downloading your Blogger archives" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2009/02/downloading-your-blogger-archives.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0MERHg8eip7ImA9WxVRGE8.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-357228904166483911</id><published>2009-01-24T13:52:00.005-05:00</published><updated>2009-01-24T15:36:45.672-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-24T15:36:45.672-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tco" /><category scheme="http://www.blogger.com/atom/ns#" term="app engine" /><category scheme="http://www.blogger.com/atom/ns#" term="PaaS" /><category scheme="http://www.blogger.com/atom/ns#" term="business case" /><category scheme="http://www.blogger.com/atom/ns#" term="cloud computing" /><category scheme="http://www.blogger.com/atom/ns#" term="aws" /><title>Business Cases and Cloud Computing</title><content type="html">I just read a very interesting &lt;a href="http://seekingalpha.com/article/100592-cloud-computing-what-are-the-barriers-to-entry-and-it-diseconomies"&gt;article&lt;/a&gt; by Gregory Ness on seekingalpha.com that talks about some of the technology trends behind cloud computing. One key quote:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt; &lt;br /&gt;Automation and control has been both a key driver and a barrier for the adoption of new technology as well as an enterprise’s ability to monetize past investments.  Increasingly complex networks are requiring escalating rates of manual intervention.  This dynamic will have more impact on IT spending over the next five years than the global recession, because automation is often the best answer to the productivity and expense challenge.&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt; &lt;br /&gt;One other cited link is to an &lt;a href="http://download.microsoft.com/download/1/9/2/192e73a4-7abb-4bad-b469-34632d54a8a6/IDC%20Whitepaper%20Demonstrating%20Business%20Value.pdf"&gt;IDC study&lt;/a&gt; that includes the following graph:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://farm4.static.flickr.com/3298/3223405278_a279aec350_o_d.gif"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 564px; height: 384px;" src="http://farm4.static.flickr.com/3298/3223405278_a279aec350_o_d.gif" border="0" alt="Graph showing that 60% of the total cost of ownership (TCO) for a server over a 3 year lifetime comes from staffing costs." /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Note that staffing accounts for 60% of the cost of maintaining a server over its lifetime. Cloud infrastructure services like &lt;a title="Click here to learn more about Amazon Web Services" href="http://aws.amazon.com/"&gt;Amazon&lt;/a&gt; &lt;a title="Click here to learn more about Amazon's Elastic Compute Cloud (EC2) service" href="http://aws.amazon.com/ec2/"&gt;EC2&lt;/a&gt; would really only save an enterprise data center on hardware setup / software install costs, which are probably, in terms of staffing, a small amount of staff time for a given server. Actually administering the server once it is running is really the bulk of the cost, and that won't go away on EC2 -- you'll still need operations staff to provision/image cloud infrastructure. EC2 makes sense if the economies of scale of AWS are such that they can achieve a lower operational cost for that other 40% than you can, or if there is a business / time-to-market value proposition that makes sense in being able to provision hardware on EC2 more rapidly than we can acquire and install hardware yourself.&lt;br /&gt;&lt;br /&gt;Given the huge economy of scale that the large cloud providers have--tens of thousands of servers, it is going to be hard to get your costs for that 40% lower than what they can achieve with their existing infrastructure automation and ability to purchase hardware in bulk, especially for a startup company whose hardware needs are initially modest. Let's guess that there's a 33% markup on cost for EC2, so when you are getting charged $0.10 per CPU hour, it's really only costing them $0.075. Let's assume a 75% &lt;a title="description of an experience curve or learning curve in business terms" href="http://en.wikipedia.org/wiki/Experience_curve_effects"&gt;experience curve&lt;/a&gt; on infrastructure (meaning, once you have doubled the number of servers you have deployed, the last server costs only 75% of what the halfway point was).&lt;br /&gt;&lt;br /&gt;By &lt;a title="article with three different estimates of the number of servers Amazon has" href="http://markmcgranaghan.com/posts/165"&gt;one estimate&lt;/a&gt;, Amazon has 30,000 servers. Now let's work backward (1/0.75 = 1.33): at 15,000 servers, their cost was $0.075 * 1.33 = $0.9975. At 7500 servers, their marginal cost was $0.9975 * 1.33 = $0.13. In other words, you'd have to be planning to deploy 15,000 servers in order to have a hope of getting your marginal cost under what they'll charge you retail.  &lt;br /&gt;&lt;br /&gt;(I think this is actually a conservative estimate: the experience/learning curve for infrastructure deployment is probably steeper than 75% due to existing hierarchical deployment patterns and a product (provisioned servers) that lends itself well to automation. Also, due to the high barrier to entry for cloud computing in terms of number of servers you need to be competitive, they can probably get away with charging an even higher markup).&lt;br /&gt;&lt;br /&gt;One corollary of this is that if you are currently running a data center with far fewer servers (i.e. the hardware is a sunk cost), &lt;span style="font-style:italic;"&gt;you might actually be better off turning your data center off and leasing from Amazon&lt;/span&gt;. Now of course, there are some things (customer credit card data, extremely sensitive business information) that you just wouldn't be willing to host somewhere outside your own data center. But that's probably a very specific set of data--host that stuff and lease the rest in the cloud, particularly if you can get adequate SLAs from your cloud vendor.&lt;br /&gt;&lt;br /&gt;So that deals with the 40% of the TCO for a server that isn't staffing. How do you cut costs on the other 60%?&lt;br /&gt;&lt;br /&gt;You won't really be able to make a dent in that 60% until you get not just to fully automated infrastructure provisioning, but until you get to fully automated software deployment and provisioning. This is not possible until you get to standardized computing platforms with specific functionality that are scale-on-demand, like &lt;a title="Click here to learn about Akamai network services" href="http://www.akamai.com/"&gt;Akamai&lt;/a&gt; &lt;a href="http://www.akamai.com/html/technology/products/netstorage.html"&gt;NetStorage&lt;/a&gt;, &lt;a href="http://aws.amazon.com/"&gt;Amazon&lt;/a&gt; &lt;a href="http://aws.amazon.com/s3"&gt;S3&lt;/a&gt;/&lt;a href="http://aws.amazon.com/ebs"&gt;EBS&lt;/a&gt;/&lt;a href="http://aws.amazon.com/sqs"&gt;SQS&lt;/a&gt;/&lt;a href="http://aws.amazon.com/simpledb/"&gt;SimpleDB&lt;/a&gt;, and &lt;a href="http://www.google.com/"&gt;Google&lt;/a&gt; &lt;a href="http://code.google.com/appengine/"&gt;AppEngine&lt;/a&gt;. These are known as "Platform-as-a-Service" (PaaS) offerings. &lt;br /&gt; &lt;br /&gt;There's a similar experience curve argument here: you could spend internal development time here to set up some kind of application deployment framework, but you'd essentially have to be willing to build and deploy within orders of magnitude the number of different apps as the Google App Engine team in order to get your costs under what Google will charge you. Unless you are in the business of directly competing with them in the PaaS market, you might as well buy from them and focus your energy on providing your unique business value, not software or hardware infrastructure. [Editor's note: this was something &lt;a href="http://blog.objectstrategy.com/"&gt;Matt Stevens&lt;/a&gt; said to me a while ago, and it wasn't until I went through the mental exercise of writing this article that I actually got it].&lt;br /&gt;&lt;br /&gt;Yesterday I implemented (not prototyped) a service in Google App Engine in about 6 hours that would cost around $400 per month (according to their recent &lt;a href="http://googleappengine.blogspot.com/2008/05/announcing-open-signups-expected.html"&gt;pricing announcements&lt;/a&gt;) if projected usage were more than double what it is now. I estimate this would require at least 10 database servers just to host the &lt;span style="font-style:italic;"&gt;data&lt;/span&gt; in a scalable, performant fashion, nevermind the REST data interface (webnodes) sitting in front of it. On Amazon EC2, that'd be $720 per month on your small instances (assuming those were even beefy enough), and per the experience curve argument above, it's probably way more than that in our data center. And that's not counting any of the reliability/load balancing infrastructure.&lt;br /&gt;&lt;br /&gt;So my open question is: how, as a software developer, can you justify &lt;span style="font-style:italic;"&gt;not&lt;/span&gt; building your app in one of these cloud frameworks?&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-357228904166483911?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/ep-1lisXhlgGZRsVosoBgAdOcKk/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ep-1lisXhlgGZRsVosoBgAdOcKk/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/ep-1lisXhlgGZRsVosoBgAdOcKk/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ep-1lisXhlgGZRsVosoBgAdOcKk/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/357228904166483911/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=357228904166483911" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/357228904166483911?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/357228904166483911?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/Xqg0KU1m3Bg/business-cases-and-cloud-computing.html" title="Business Cases and Cloud Computing" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2009/01/business-cases-and-cloud-computing.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUQCQn47cCp7ImA9WxVREU4.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-528358299708941850</id><published>2009-01-12T18:53:00.033-05:00</published><updated>2009-01-16T15:22:43.008-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-16T15:22:43.008-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="REST API" /><category scheme="http://www.blogger.com/atom/ns#" term="rest" /><category scheme="http://www.blogger.com/atom/ns#" term="restful web services" /><category scheme="http://www.blogger.com/atom/ns#" term="web applications" /><title>Websites are also RESTFul Web Services</title><content type="html">I have been reading the Richardson and Ruby book &lt;span style="font-style: italic;"&gt;&lt;a title="Click here to read about this book on O'Reilly" href="http://oreilly.com/catalog/9780596529260/"&gt;RESTful Web Services&lt;/a&gt;&lt;/span&gt; and recently had an epiphany: if you design a RESTful web &lt;em&gt;site&lt;/em&gt; it &lt;b&gt;is also a RESTful web API&lt;/b&gt;. In this post I'll show exactly how that works and how you can use this to rapidly build a prototype of a modern web application.&lt;br /&gt;&lt;br /&gt;First of all, let's start with a very simple application concept and build it from the ground up. Let's consider a simple application that lets you keep a list of favorite items. The resources we'll want to model are:&lt;br /&gt;&lt;ul class="codeartisan"&gt;&lt;br /&gt;&lt;li class="codeartisan"&gt;a favorite item&lt;/li&gt;&lt;br /&gt;&lt;li class="codeartisan"&gt;a list of a user's favorite items&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;We'll assign the following URLs:&lt;br /&gt;&lt;ul class="codeartisan"&gt;&lt;br /&gt;&lt;li class="codeartisan"&gt;&lt;tt&gt;/favorites/1234&lt;/tt&gt; for favorite item with primary key 1234&lt;/li&gt;&lt;br /&gt;&lt;li class="codeartisan"&gt;&lt;tt&gt;/favorites&lt;/tt&gt; is the list of everyone's favorite items&lt;/li&gt;&lt;br /&gt;&lt;li class="codeartisan"&gt;&lt;tt&gt;/favorites/-/{owner}joe@example.com&lt;/tt&gt; (or its URL-encoded equivalent) is the list of items belonging to our friend Joe (this is a URL format inspired by &lt;a title="Google home page" href="http://www.google.com/"&gt;Google's&lt;/a&gt; &lt;a href="http://www.blogger.com/Protocol%20specification%20for%20GData"&gt;GData protocol&lt;/a&gt;).&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Now, we'll support the following &lt;a title="definition of HTTP methods" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9"&gt;HTTP methods&lt;/a&gt;:&lt;br /&gt;&lt;ul class="codeartisan"&gt;&lt;br /&gt;&lt;li class="codeartisan"&gt;You can GET and DELETE an individual item (we could allow PUT if we wanted to allow editing, but we'll keep the example simple)&lt;/li&gt;&lt;br /&gt;&lt;li class="codeartisan"&gt;You can create a new favorite item with a POST to /favorites.&lt;/li&gt;&lt;br /&gt;&lt;li class="codeartisan"&gt;You can GET the list of a user's favorites.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Ok. Now we want to rapidly prototype this so we know if we have the resources modelled correctly. Fire up your favorite web application framework (&lt;a title="Ruby on Rails home page" href="http://rubyonrails.org/"&gt;Ruby on Rails&lt;/a&gt;, &lt;a title="home page for the Django Python web application framework" href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt;, &lt;a href="http://www.blogger.com/Click%20here%20for%20information%20on%20the%20Spring%20web%20application%20framework%20for%20Java"&gt;Spring&lt;/a&gt;, etc.) and map those URLs to controllers. Now most of these frameworks let you fill in implementations for the various HTTP methods. We'll make a minor simplification and allow "overloaded POST" where we allow passing a URL parameter to POST to specify PUT and DELETE (e.g. "_method=DELETE"). We can implement the proper HTTP method but we'll allow you to use POST to do it too; browsers and some Javascript HTTP implementations can only do GET and POST.&lt;br /&gt;&lt;br /&gt;Ok, now a funny thing happens: it you render an HTML response for everything, you can start playing with your API in your browser! In particular, when we render the list of items, we will naturally put the text of those items on the page, but we can also throw the following HTML snippet at the top of the page:&lt;br /&gt;&lt;br /&gt;&lt;div style="background-color: #bbbbbb; margin-left: 20px; padding-left: 10px; padding-top: -1.3em; padding-bottom: -1.3em; width: 600px;"&gt;&lt;br /&gt;&lt;pre style="margin-top: -2.6em; margin-bottom: -1.3em;"&gt;&lt;br /&gt;&amp;lt;p&gt;Add a new favorite:&amp;lt;/p&gt;&lt;br /&gt;&amp;lt;form action="/items" method="post"&gt;&lt;br /&gt;  &amp;lt;input type="text" name="itemname"/&gt;&lt;br /&gt;  &amp;lt;input type="submit" value="Add"/&gt;&lt;br /&gt;&amp;lt;/form&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;We can also add the following form after each item's text:&lt;br /&gt;&lt;br /&gt;&lt;div style="background-color: #bbbbbb; margin-left: 20px; padding-left: 10px; padding-top: -1.3em; padding-bottom: -1.3em; width: 600px;"&gt;&lt;br /&gt;&lt;pre style="margin-top: -2.6em; margin-bottom: -1.3em;"&gt;&lt;br /&gt;&amp;lt;!-- use a specific item's URL for the action --&gt;&lt;br /&gt;&amp;lt;form action="/items/1234" method="post"/&gt;&lt;br /&gt; &amp;lt;input type="hidden" name="_method" value="DELETE"/&gt;&lt;br /&gt; &amp;lt;input type="submit" value="Delete"/&gt;&lt;br /&gt;&amp;lt;/form&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;which gives us this ugly beastie:&lt;br /&gt;&lt;br /&gt;&lt;div style="margin-left: 20px; padding-left: 10px; width: 450px; border: thin solid black;"&gt;&lt;br /&gt;&lt;b&gt;A Few of My Favorite Things:&lt;/b&gt;&lt;br/&gt;&lt;br /&gt;&lt;form action="http://www.example.com/" method="post"&gt;&lt;br /&gt;Add a new favorite: &lt;input type="text" name="itemname"/&gt;&lt;input type="submit" value="Add"/&gt;&lt;br /&gt;&lt;/form&gt;&lt;br /&gt;&lt;form action="http://www.example.com/" method="post"&gt;&lt;br /&gt;&lt;ul style="margin-top: -1.3em; margin-bottom: -2.6em;"&gt;&lt;br /&gt;&lt;li&gt;raindrops on roses &lt;input type="submit" value="Delete"/&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;whiskers on kittens &lt;input type="submit" value="Delete"/&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;bright copper kettles &lt;input type="submit" value="Delete"/&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;warm woolen mittens &lt;input type="submit" value="Delete"/&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;wild geese that fly with the moon on their wings &lt;input type="submit" value="Delete"/&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;/form&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Now, one key item is that when you render the result page for adding an item, you can send a 201 (Created) response and say something like "item added", throwing in a link back to the list page. The whole HTML response might be nothing more than:&lt;br /&gt;&lt;br /&gt;&lt;div style="background-color: #bbbbbb; margin-left: 20px; padding-left: 10px; padding-top: -1.3em; padding-bottom: -1.3em; width: 600px;"&gt;&lt;br /&gt;&lt;pre style="margin-top: -2.6em; margin-bottom: -1.3em;"&gt;&lt;br /&gt;&amp;lt;html&gt;&lt;br /&gt;  &amp;lt;head&gt;&amp;lt;/head&gt;&lt;br /&gt;  &amp;lt;body&gt;&lt;br /&gt;  &amp;lt;p&gt;I created your item &amp;lt;a href="/items/2345"&gt;here&amp;lt;/a&gt;.&amp;lt;/p&gt;&lt;br /&gt;  &amp;lt;p&gt;All your items are &amp;lt;a href="/items/-/{owner}joe@example.com"&gt;here&amp;lt;/a&gt;.&amp;lt;/p&gt;&lt;br /&gt;  &amp;lt;/body&gt;&lt;br /&gt;&amp;lt;/html&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;We similarly want to render a confirmation page after a DELETE. This makes for an awkward user experience "add, ok, add, ok,..." but you'll notice that the back and forward buttons on your browser actually work without having to rePOST any exchanges. &lt;br /&gt;&lt;br /&gt;[Side note: you could, instead of returning a success page, return a 302 that lands you back on the list page, which maybe gets you closer to what you wanted from a user experience, but this is precisely what will break your browser's back button and make you rePOST.]&lt;br /&gt;&lt;br /&gt;Now you also have the interesting property that all the &lt;em&gt;links&lt;/em&gt; on your site are safe (without side effects) GETs, and all the &lt;em&gt;buttons&lt;/em&gt; are potentially destructive (write operations of one sort or another). I say only "potentially" because you might have a search form with &lt;tt&gt;action="get"&lt;/tt&gt; to do a query, and not all of your POST/PUT/DELETEs will actually change anything.&lt;br /&gt;&lt;br /&gt;At any rate, at this point, you have a functionally working website that someone could use, if somewhat awkwardly. Plus, if you have my frontend aesthetic design sensibilities, your users will have the pleasure of suppressing a gag reflex while using your site.&lt;br /&gt;&lt;br /&gt;So let's spruce this up a little bit. Now, on the HTML page for the list of favorites, we can apply some Javascript. At a first blush, we can hide the delete buttons until a mouseover, which cleans things up somewhat. But the real magic happens when we attach an AJAX event to the delete buttons. Now the script can actually do &lt;em&gt;the very same POST&lt;/em&gt; that the form would have done, and then check the &lt;a title="description of HTTP status codes" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10"&gt;HTTP status code&lt;/a&gt;, removing the item text from the DOM on success. &lt;br /&gt;&lt;br /&gt;Suddenly, the user never leaves that list page, and we haven't had to change any of the rest of the API -- just the HTML representation of that list page. The AJAX call doesn't care if it gets HTML back (in this case), it just cares about the response code. Now we have the nice AJAXy experience we would expect, but oddly enough you still have a website that will work for people with Javascript disabled.&lt;br /&gt;&lt;br /&gt;The last step towards finishing out your API is probably simply to make structured versions of your representations available (e.g. &lt;a title="description of Javascript Object Notation (JSON)" href="http://www.json.org/"&gt;JSON&lt;/a&gt; or &lt;a title="description of the eXtensible Markup Language (XML)" href="http://en.wikipedia.org/wiki/XML"&gt;XML&lt;/a&gt; formats like &lt;a title="description of the Atom syndication format" href="http://www.atomenabled.org/developers/syndication/atom-format-spec.php"&gt;Atom&lt;/a&gt;) with an optional parameter like "?format=json". Now all of your client-side functions can call URLs with the appropriate format on them and get well-structured data, and everyone else gets HTML.&lt;br /&gt;&lt;br /&gt;Well, I guess that's the second to last step. You probably actually want to apply some graphic design and CSS to your site too...&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-528358299708941850?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/Ptw8Roofm2OrYkGhbSugogGTjf8/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Ptw8Roofm2OrYkGhbSugogGTjf8/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/Ptw8Roofm2OrYkGhbSugogGTjf8/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Ptw8Roofm2OrYkGhbSugogGTjf8/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/528358299708941850/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=528358299708941850" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/528358299708941850?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/528358299708941850?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/LZ_6-0-roIs/websites-are-also-restful-web-services.html" title="Websites are also RESTFul Web Services" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2009/01/websites-are-also-restful-web-services.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEAEQXgyeCp7ImA9WxRRE00.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-1292130154490868674</id><published>2008-09-19T22:24:00.013-04:00</published><updated>2008-09-24T21:38:20.690-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-09-24T21:38:20.690-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="simple mac backups" /><category scheme="http://www.blogger.com/atom/ns#" term="mac osx backups" /><category scheme="http://www.blogger.com/atom/ns#" term="backups for mac" /><category scheme="http://www.blogger.com/atom/ns#" term="ssh" /><category scheme="http://www.blogger.com/atom/ns#" term="rsync" /><category scheme="http://www.blogger.com/atom/ns#" term="hosting" /><title>Simple Backups for your Mac</title><content type="html">You are probably well aware of the need for offsite backups; as a technology professional this is one of the first arrangements I look into for any permanent storage of business information. When I started two years ago for an internal "startup" for a large company, one of the first things we did was set up an SVN repository and then work out an arrangement with an offsite data storage provider. However, the cobbler's children have no shoes: I've never set up a proper backup scheme for my own data at home, and it's about time to take care of business.&lt;br /&gt;&lt;br /&gt;Fortunately, now that we've moved to using Macs at home and with the advent of cheap UN*X hosting providers, it's about time I stopped putting this off. The scheme here is pretty simple: get a hosted Linux server from someone like 1and1.com, dreamhost.com, or rackspace.com where the storage is backed up and they take care of security updates for the OS. Then set up a pretty simple combination of the UN*X utilities rsync, ssh, cron, and bash scripts to get secure nightly backups going. Just to make it more fun, I'm going to challenge myself to have this all working in under an hour! I'll keep notes as I'm doing it as to how long it takes, not counting the writeup before or afterward.&lt;br /&gt;&lt;br /&gt;I decided to register a domain name with a hosting provider, since it was included. My basic requirements were:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;SSH access&lt;/li&gt;&lt;br /&gt;&lt;li&gt;rsync installed&lt;/li&gt;&lt;br /&gt;&lt;li&gt;enough storage for my data&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Be sure to acquire the following information from your hosting provider:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;username/password with SSH access (preferably root, if you want to use the server for other purposes, but this is not necessary)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;IP address&lt;/li&gt;&lt;br /&gt;&lt;li&gt;SSH host key of the server&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;I ended up registering a new domain with at dreamhost.com at $9.95/month. As it happened, DreamHost was running a promotion with unlimited disk space and bandwidth for the lifetime of my account. Score! I did have to email tech support to get the ssh host key. If you find yourself in a similar position, you can ask for the output of:&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ ssh-keygen -l -f ssh_host_rsa_key.pub&lt;br /&gt;2048 0e:c2:f6:f4:d9:86:9d:4b:c4:3d:77:e7:a4:bb:59:14 ssh_host_rsa_key.pub&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Ok, great! Now you have a destination for your offsite storage. Next step is to make sure we can securely log in over the network (we'll use ssh for this). On the Mac you want to back up, open up a Terminal window, and ssh into your server using your username and the server's hostname, as in the following. &lt;b&gt;N.B. Do not finish connecting if the ssh server host key you got from your hosting provider does not match the key you see when you try this!&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;macbook:~ jonm$ ssh jonm@backup.dreamhost.com&lt;br /&gt;The authenticity of host 'backup.dreamhost.com (67.205.39.2)' can't be established.&lt;br /&gt;RSA key fingerprint is 0e:c2:f6:f4:d9:86:9d:4b:c4:3d:77:e7:a4:bb:59:14.&lt;br /&gt;Are you sure you want to continue connecting (yes/no)? yes&lt;br /&gt;Warning: Permanently added 'backup.dreamhost.com,67.205.39.2' (RSA) to the list of known hosts.&lt;br /&gt;jonm@backup.dreamhost.com's password:&lt;br /&gt;[backup]$&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Ok, so far so good. Now we need to make sure we can do it without needing a password; this is where user ssh keys come into play. First, let's create an ssh key to use for backups. We'll want to do this as the root user on our Mac, so that when we run the backup script out of cron, we won't run into permissions problems. You can use the "sudo" command to become root on your Mac:&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;macbook:~ jonm$ sudo su -&lt;br /&gt;&lt;br /&gt;WARNING: Improper use of the sudo command could lead to data loss&lt;br /&gt;or the deletion of important system files. Please double-check your&lt;br /&gt;typing when using sudo. Type "man sudo" for more information.&lt;br /&gt;&lt;br /&gt;To proceed, enter your password, or type Ctrl-C to abort.&lt;br /&gt;&lt;br /&gt;Password: &amp;lt;enter jonm's password on my mac&amp;gt;&lt;br /&gt;macbook:~ root#&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Now we need to create an SSH public/private key pair; this is a similar concept to PGP email encryption/signing; you can read a really interesting description of the chronology behind public key cryptography in the book &lt;a href="http://www.amazon.com/Crypto/dp/B000OIZV9I/ref=sr_1_11?ie=UTF8&amp;s=books&amp;qid=1221879031&amp;sr=8-11"&gt;&lt;i&gt;Crypto&lt;/i&gt;&lt;/a&gt; by Steven Levy. We'll keep the private key locally on our Mac, and take a copy of the public key and copy it securely up to our backup server; then ssh will use the private key when we connect, allowing the backup server to verify using the public key that we are who we say we are, without having to send a password. Nice.&lt;br /&gt;&lt;br /&gt;Specifically, we will want to do the following (still as root):&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;macbook:~ root# mkdir .ssh&lt;br /&gt;macbook:~ root# chmod 700 .ssh&lt;br /&gt;macbook:~ root# ls -ld .ssh&lt;br /&gt;drwx------  2 root  wheel  68 Sep 19 22:01 .ssh&lt;br /&gt;macbook:~ root# ssh-keygen -t dsa&lt;br /&gt;Generating public/private dsa key pair.&lt;br /&gt;Enter file in which to save the key (/var/root/.ssh/id_dsa):&lt;br /&gt;Enter passphrase (empty for no passphrase):&lt;br /&gt;Enter same passphrase again:&lt;br /&gt;Your identification has been saved in /var/root/.ssh/id_dsa.&lt;br /&gt;Your public key has been saved in /var/root/.ssh/id_dsa.pub.&lt;br /&gt;The key fingerprint is:&lt;br /&gt;fd:47:1d:a6:ac:d0:7d:fb:a5:17:cf:e2:8a:93:a5:30 root@jon-moores-macbook.local&lt;br /&gt;macbook:~ root#&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Use an empty passphrase (i.e. just hit return when prompted for the passphrase), as this will allow the ssh program to load the key without interaction from you. Also note, however, that anyone who gets root access to your Mac will be able to ssh into your backup server at will. Given that our backup server contains a copy of what this&lt;br /&gt;would-be hacker would be able to see on the actual Mac anyway, I don't really see this being a big risk....&lt;br /&gt;&lt;br /&gt;Now, we need to copy the public key over to the backup server:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;macbook:~ root# scp .ssh/id_dsa.pub jonm@backup.dreamhost.com:&lt;br /&gt;jonm@backup.dreamhost.com's password:&lt;br /&gt;id_dsa.pub                             100%  619     0.6KB/s   00:00&lt;br /&gt;macbook:~ root#&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;You'll have to verify the server SSH key one more time, because now you are connecting from root rather than from your normal user account. Now we'll tell the backup host to accept a login from this key pair:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[backup]$ mkdir -p .ssh&lt;br /&gt;[backup]$ chmod 700 .ssh&lt;br /&gt;[backup]$ cat id_dsa.pub &gt; ~/.ssh/authorized_keys&lt;br /&gt;[backup]$ chmod 600 ~/.ssh/authorized_keys&lt;br /&gt;[backup]$ exit&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Now, we should be able to log in without a password from our Mac:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;macbook:~ root# ssh jonm@backup.dreamhost.com&lt;br /&gt;[backup]$&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Sweet. Now we create a directory where our mirrored filesystems will live:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[backup]$ mkdir mac-backups&lt;br /&gt;[backup]$ chmod 700 mac-backups&lt;br /&gt;[backup]$ exit&lt;br /&gt;macbook:~ root#&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;The utility we'll use to do the mirroring is the rsync utility, which can be invoked to run securely over ssh. This actually makes a nice backup utility for regular use, as the rsync protocol is actually pretty smart about being able to find just the small subsets of data that changed since the last sync; after the first big sync, for most&lt;br /&gt;personal file use, there won't be much work to do every night.&lt;br /&gt;&lt;br /&gt;For now, let's set up a test directory on our local Mac.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;macbook:~ root# mkdir /tmp/back-me-up&lt;br /&gt;macbook:~ root# echo "data" &gt; /tmp/back-me-up/afile.txt&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Now, to make the magic happen, we do this:&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;macbook:~ root# rsync -avz -e ssh /tmp/back-me-up jonm@backup.dreamhost.com:mac-backups&lt;br /&gt;building file list ... done&lt;br /&gt;back-me-up/&lt;br /&gt;back-me-up/afile.txt&lt;br /&gt;&lt;br /&gt;sent 116 bytes  received 40 bytes  62.40 bytes/sec&lt;br /&gt;total size is 5  speedup is 0.03&lt;br /&gt;macbook:~ root#&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Now we can keep a window open on our backups host, and we should see everything show up there:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[backup]$ ls -lR mac-backups&lt;br /&gt;mac-backups:&lt;br /&gt;total 4&lt;br /&gt;drwxr-xr-x 2 jonm pg1807352 4096 2008-09-19 19:11 back-me-up/&lt;br /&gt;&lt;br /&gt;mac-backups/back-me-up:&lt;br /&gt;total 4&lt;br /&gt;-rw-r--r-- 1 jonm pg1807352 5 2008-09-19 19:11 afile.txt&lt;br /&gt;[backup]$&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Just for fun, run the same rsync command above and see that nothing happens if there have been no changes (or rather, just that a very small amount of data gets exchanged to verify no changes).&lt;br /&gt;&lt;br /&gt;Let's just make sure changes show up:&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;macbook:~ root# echo "changed-data" &gt; /tmp/back-me-up/afile.txt&lt;br /&gt;macbook:~ root# rsync -avz -e ssh /tmp/back-me-up jonm@backup.dreamhost.com:mac-backups&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;(other window)&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[backup]$ cat mac-backups/back-me-up/afile.txt&lt;br /&gt;changed-data&lt;br /&gt;[backup]$&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Ok, looking good. Next step is to identify all the directories you want to back up; let's keep a list of them in a config file on our mac:&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;pre&gt;&lt;br /&gt;macbook:~ root# mkdir -p /usr/local/etc&lt;br /&gt;macbook:~ root# cat - &gt; /usr/local/etc/backups.conf&lt;br /&gt;/Users/jonm/Documents&lt;br /&gt;/tmp/back-me-up&lt;br /&gt;macbook:~ root#&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Note that it is important *not* to have trailing slashes on these directory names, as this changes rsync's behavior slightly in a way that you will probably find annoying (it won't copy the directory name over, just the contents).&lt;br /&gt;&lt;br /&gt;Ok, now the next step is to set up a script that can sync each of the directories:&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;macbook:~ root# mkdir -p /usr/local/bin&lt;br /&gt;macbook:~ root# touch /usr/local/bin/do-backups&lt;br /&gt;macbook:~ root# chmod 700 /usr/local/bin/do-backups&lt;br /&gt;macbook:~ root# cat - &gt; /usr/local/bin/do-backups&lt;br /&gt;#!/bin/sh&lt;br /&gt;for dir in `cat /usr/local/etc/backups.conf`; do&lt;br /&gt;  rsync -avz -e ssh $dir jonm@backups.dreamhost.com:mac-backups&lt;br /&gt;done&lt;br /&gt;macbook:~ root#&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Now we run it once by hand to make sure it works:&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;macbook:~ root# /usr/local/bin/do-backups&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Finally, we install this in root's crontab as follows:&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;macbook:~ root# crontab -l &gt; /tmp/root.cron&lt;br /&gt;macbook:~ root# cat - &gt;&gt; /tmp/root.cron&lt;br /&gt;# take a backup every day at 3am&lt;br /&gt;0 3 * * * /usr/local/bin/do-backups &gt;/dev/null&lt;br /&gt;macbook:~ root# crontab /tmp/root.cron&lt;br /&gt;macbook:~ root# rm /tmp/root.cron&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Nice and simple. Now the backups are off and running every night without your intervention.&lt;br /&gt;&lt;br /&gt;If you ever need to restore from the backup, you can always reverse the rsync process like this:&lt;br /&gt;&lt;br /&gt;&lt;div style="margin: 5px; border: thin solid black; padding: 5px;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;macbook:~ root# rsync -avz -e ssh jonm@backup.dreamhost.com:mac-backups/back-me-up /tmp&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;for each of the directories you have backed up over there.&lt;br /&gt;&lt;br /&gt;Enjoy, and sleep well tonight....&lt;br /&gt;&lt;br /&gt;P.S. Total elapsed time for the exercise was 2 hours from the time I placed the hosting order to the time the crontab was installed, but I took a one hour break in the middle for dessert and bedtime with the kids. So I'll claim this really did only take one hour of "CPU time" for me.&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-1292130154490868674?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/ioZA2M7Lq7mcJMCDEuMOrGokfco/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ioZA2M7Lq7mcJMCDEuMOrGokfco/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/ioZA2M7Lq7mcJMCDEuMOrGokfco/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ioZA2M7Lq7mcJMCDEuMOrGokfco/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/1292130154490868674/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=1292130154490868674" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/1292130154490868674?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/1292130154490868674?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/DZi-uUzcCLk/simple-backups-for-your-mac.html" title="Simple Backups for your Mac" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2008/09/simple-backups-for-your-mac.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DE8MR30-fCp7ImA9WxRTGU4.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-21822464782177675</id><published>2008-09-02T15:50:00.009-04:00</published><updated>2008-09-09T01:08:06.354-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-09-09T01:08:06.354-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="process improvement" /><category scheme="http://www.blogger.com/atom/ns#" term="metrics" /><category scheme="http://www.blogger.com/atom/ns#" term="lean engineering" /><category scheme="http://www.blogger.com/atom/ns#" term="kaizen" /><title>Measure your improvements</title><content type="html">Metrics are an important part of any development group's toolset. If we want to continually improve our ability to develop software (through a &lt;a href="http://en.wikipedia.org/wiki/Lean_manufacturing" title="get more information about lean engineering and manufacturing methods"&gt;lean engineering&lt;/a&gt; &lt;a href="http://en.wikipedia.org/wiki/Kaizen" title="get more information about kaizen, a process of continuous improvement"&gt;&lt;i&gt;kaizen&lt;/i&gt;&lt;/a&gt; approach, or simply as a &lt;a href="http://home.nycap.rr.com/klarsen/learnorg/" title="get more information about theories about how organizations learn as individual entities"&gt;learning organization&lt;/a&gt;), then we need to have a way to figure out:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;what parts of our process need improvement?&lt;br /&gt;&lt;li&gt;when we make a change, did it help or hurt?&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;This is where process metrics come into play. I'll start with my definition of a &lt;em&gt;metric&lt;/em&gt;, which is a numerical measurement of something. If you can count it, it can be a metric. So "number of outstanding bugs" is a metric, but "software quality" is not. The term "quantitative metric" is redundant, and "qualitative metric" is an oxymoron.&lt;br /&gt;&lt;br /&gt;There are generally two types of metrics we can capture:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;causal metrics&lt;/b&gt;: these are metrics that have a direct business impact: for example, ROI for a feature, unique monthly visitors, click-through ad rate, &lt;em&gt;etc&lt;/em&gt;.&lt;br /&gt;&lt;li&gt;&lt;b&gt;symptomatic metrics&lt;/b&gt;: these are metrics that do not directly affect ROI (although we might &lt;em&gt;believe&lt;/em&gt; they do) but are downstream indicators for one or more causal metrics. The number of outstanding bugs in a product, the number of bugs caught in a certain phase of development, percentage of code covered by unit tests, &lt;em&gt;etc&lt;/em&gt;. are all symptomatic metrics.&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;My general observation, based on reading articles around the use of metrics for improving your processes, is that a lot of metrics-based improvement projects fail to distinguish between these two types of metrics. Partly, I think this is because while the causal metrics properly align your improvement efforts with your business's interests, they are also harder to define and measure. By contrast, a lot of symptomatic metrics are easy to find and measure, but their relationship to the business may be less clear.&lt;br /&gt;&lt;br /&gt;For example, consider the balance between software quality and time to market. You can take a longer time when developing a feature or product to reduce the number of bugs that show up at deployment, or you can ship a feature more quickly, knowing that there may be both known and unknown bugs present. In this case, you can measure both number of known bugs at deployment time, and you can measure overall time-to-production for a feature (time from feature conception to deployment).&lt;br /&gt;&lt;br /&gt;Now, if you can decrease time-to-production without increasing the bugginess of your code, that's a win. Similarly, if you can reduce bugginess without lengthening your production time, that's a win. However, both of these things will probably require some effort to implement. Another interesting possibility would be to simply make an adjustment of where you sit on this balance. For example, simply spend more time looking for and fixing bugs in your QA phase, to tradeoff fewer bugs for a slower time-to-market. Or vice versa to get to market more quickly, possibly with more bugs. Both of these adjustments are probably relatively painless to implement, in that no one has to change what they do, just how long they do it for.&lt;br /&gt;&lt;br /&gt;So the question is, which one of these things ought we to do? My argument is that bugginess and time-to-production, both being &lt;em&gt;symtomatic&lt;/em&gt; metrics, don't give us the answer directly. It all depends on our product environment. For example, when producing software to run medical equipment, a company reputation for quality might be more important than shipping new features quickly; or, in a highly innovative internet space, time to market might be king in terms of how much market share you can capture.&lt;br /&gt;&lt;br /&gt;It's management's job to both help implement win-win changes as well as to set the "slider" of the quality/time tradeoff at the right spot. The trick, of course, is that it might be hard to measure this directly; there are several symptomatic things we could measure, including:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;time spent in development&lt;br /&gt;&lt;li&gt;time spent in QA&lt;br /&gt;&lt;li&gt;time spent in deployment&lt;br /&gt;&lt;li&gt;number of outstanding bugs&lt;br /&gt;&lt;li&gt;profit for the product at a given point in time&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Now really, the profit over some window of time (e.g. for a website, revenue vs. development spend for a given month) is the thing we want to optimize. The interesting idea here is for management to be able to run a series of experiments: if I increase/decrease QA or development time, how does it affect ROI for my product? How does the relative bugginess of a release affect its profitability? For certain "sliders" in the business, it is relatively simple to take a series of measurements to find a current "sweet spot".&lt;br /&gt;&lt;br /&gt;An interesting idea here is that sometimes we work through things backwards. For example, we try to estimate "how long will it take to fully regression test a release", or "how long will it take to code up a feature", rather than "how buggy will the release be if we test it for X amount of time", or "how much of this functionality can you develop in X amount of time." In other words, rather than deriving the time-to-market from a set of estimates for all the steps, instead &lt;em&gt;set&lt;/em&gt; the time to market by timeboxing those steps, and see what the outcome is. This is a powerful notion of metrics-based management that is hinted at (in the notion of &lt;a href="http://www.controlchaos.org/" title="find out more about the scrum development framework"&gt;Scrum&lt;/a&gt; timeboxed iterations) but which I have not seen explicitly suggested anywhere[&lt;a href="#footnote1"&gt;1&lt;/a&gt;]. (Please post all the references to things that I've missed in the comments section--I'm sure there are plenty).&lt;br /&gt;&lt;br /&gt;At the end of the day, however, it is hard to optimize things we can't measure. I think important metrics to gather are:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;the levers we have available to manipulate our process (e.g. timeboxing)&lt;br /&gt;&lt;li&gt;causal metrics that affect our business (ROI, product profit)&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;We need to be aware which metrics are causal, and which are merely symptomatic, so that we are measuring things that directly affect the business somehow. This approach permits empirical management--adjust something you can control, see how it affects your causal metrics, rinse, repeat.&lt;br /&gt;&lt;br /&gt;&lt;hr/&gt;&lt;br /&gt;&lt;br /&gt;&lt;a name="footnote1"&gt;[1]&lt;/a&gt; Scrum timeboxes an entire iteration, but does not timebox an individual feature, so a team may be able to spend all their time on one feature, or spread their effort across many features. The closest thing I've seen here is the notion of the "&lt;b&gt;S&lt;/b&gt;mall" in &lt;a href="http://codeartisan.blogspot.com/2008/02/investing-in-user-stories.html" title="find out more about the INVEST model of desirable user story characteristics"&gt;INVEST user stories&lt;/a&gt;, where stories are limited to a certain amount of complexity. However, the story points in this case are still estimates of the work involved, rather than timeboxes around how much time to spend implementing a feature; the "small" requirement is really to permit more accurate estimation rather than to timebox the amount of effort (although it does secondarily have this effect, I've not seen this stressed in articles about this).&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-21822464782177675?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/ZahMSIme_-KX_bz11tjom-WKejc/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ZahMSIme_-KX_bz11tjom-WKejc/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/ZahMSIme_-KX_bz11tjom-WKejc/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ZahMSIme_-KX_bz11tjom-WKejc/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/21822464782177675/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=21822464782177675" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/21822464782177675?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/21822464782177675?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/JUKu3PY5jKo/measure-your-improvements.html" title="Measure your improvements" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2008/09/measure-your-improvements.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEcHSHw8eip7ImA9WxdUGE0.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-6241208165467935170</id><published>2008-08-03T12:57:00.003-04:00</published><updated>2008-08-03T18:20:39.272-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-08-03T18:20:39.272-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="configuration debt" /><category scheme="http://www.blogger.com/atom/ns#" term="transition debt" /><category scheme="http://www.blogger.com/atom/ns#" term="architectural debt" /><category scheme="http://www.blogger.com/atom/ns#" term="technical debt" /><title>Cracking down on technical debt</title><content type="html">"Simplicity is the ultimate sophistication." --Leonardo da Vinci.&lt;br /&gt;&lt;br /&gt;"Everything should be made as simple as possible, but no simpler." --Albert Einstein&lt;br /&gt;&lt;br /&gt;"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away."&lt;br /&gt;--Antoine de Saint-Exupery&lt;br /&gt;&lt;br /&gt;I've written before about the notion of &lt;a href="http://codeartisan.blogspot.com/2007/12/whats-apr-on-your-technical-debt.html"&gt;technical debt&lt;/a&gt;. In this post, I want to discuss a few specific sources of technical debt that are easy to accrue, particularly in an agile, iteration-based setting.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Incomplete technology transitions&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;These can arise when a technical decision gets made to transition from one technology/architecture/design to another, and the transition happens incrementally. What can end up happening is that an agile team, say one operating under the &lt;a href="http://www.controlchaos.com/"&gt;Scrum&lt;/a&gt; framework, does not complete its incremental transition during the current sprint. Now, although the code is in a working state, there is a good chunk of technical debt arising from having code operating under two separate systems. This &lt;em&gt;transition debt&lt;/em&gt; is problematic for a few reasons:&lt;br /&gt;&lt;br /&gt;First, this can complicate debugging efforts -- when there is a problem with the system, someone has to determine under which scheme the code in question was written. Typically this can mean looking in two different source code hierarchies, or looking through two separate sets of configuration. The system is, as a result, more complicated than it needs to be.&lt;br /&gt;&lt;br /&gt;Secondly, this can be an attractor for additional debt; if the old system is still around, and a developer is more familiar with the old system than the new, there is a very strong temptation to make changes/additions in the old system. This work simply adds to the outstanding transition work, and despite the developer's familiarity, is likely to be implemented in a more difficult or less efficient way (assuming, of course, there were valid technical reasons for making the transition in the first place). &lt;br /&gt;&lt;br /&gt;Finally, this can cause extra work to happen during feature development that touches/interacts with the subsystem in transition, because either the cooperating subsystems have to special case two different interaction styles, or an adaptation layer has to be built to handle both subsystems and abstract their existence away from clients' concerns. Either way, you are writing more code that you would have if you had completed the transition and you just had one implementation of the subsystem.&lt;br /&gt;&lt;br /&gt;Teams working on an iteration-based methodology need to do several things to avoid the pitfalls from transition debt:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;when a technical decision for a transition has been made, it must be communicated clearly to the whole development team, including the reasons for the transition. This can help prevent the unintentional accrual of additional transition debt.&lt;br /&gt;&lt;li&gt;plan for more refactoring time when signing up for work, to leave time to complete transitions before an iteration ends.&lt;br /&gt;&lt;li&gt;communicate the existence of the transition debt to the Product Owner at the review, so that completing the transition can be scheduled as a backlog item. Furthermore, stress the priority of this carryover work to ensure that the transition debt exists for the shortest amount of time possible.&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Obsolete/extraneous configuration&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;We'll call this type of technical debt &lt;em&gt;configuration debt&lt;/em&gt;. There are a couple of sources of this type of debt:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;transitional runtime configuration that still exists after the transition. For example, when a data partner was making an id space transition to extend the length of their ids, we had a flag to govern whether to use old or new ids with that partner, so that we could decouple our code releases from the partner's transitions. Over a year later, the flag still exists, but it is always set to use "new" ids, so there is certainly unneeded code to handle this.&lt;br /&gt;&lt;li&gt;exposing properties that would only change with a code drop as runtime configuration. In this case, the values of the properties would really only change if we rolled a new code release, so they could just as easily be compile-time constants that would not require the scaffolding to make them runtime properties, no matter how simple that scaffolding might be.&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;Unnecessary code hurts you in several ways:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;it took someone time to write it in the first place&lt;br /&gt;&lt;li&gt;you have to compile it or run its unit tests over and over again while you're developing (death of a thousand cuts)!&lt;br /&gt;&lt;li&gt;people need to keep it in their mental model of the system instead of leaving room for parts of the system that actually do something useful&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;The easiest way to prevent the accrual of configuration debt is to review any new runtime configuration parameters at the end of each sprint (which you probably have to do anyway so your operations folk know how to properly configure the new system). Then, where possible:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;turn as many runtime parameters into compile-time constants as possible&lt;br /&gt;&lt;li&gt;ask under what conditions the parameters will no longer be needed (for example, for configuration that assists with an external transition, add an item to the product backlog to clean up the codebase after the transition is successful)&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Obsolete/insufficient architecture and design&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;&lt;em&gt;Architectural debt&lt;/em&gt; is probably the most nefarious, because this is debt that doesn't actually get created when the code is first written. Instead, this is usually caused by external factors such as:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;business environment changes and expected traffic is significantly different than originally anticipated. This can leave you either with an overly complex, over-engineered system, or with a too-simple system that can't easily scale.&lt;br /&gt;&lt;li&gt;product direction changes, and the architecture is not flexible along the new axis of change, so that new development is overly difficult.&lt;br /&gt;&lt;li&gt;expected performance of a new provisional architecture is invalidated by experience&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Basically, as soon as you realize you need to change your architecture, you have magically "created" technical debt out of all the code that depended on the first architecture. In reality, this debt is probably unavoidable, and what you've really done is convert your inability to perfectly predict the future into a set of work that incorporates new knowledge about the problem domain.&lt;br /&gt;&lt;br /&gt;This can also be hard to identify by scrutinizing the code, but there are some external symptoms of it:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;difficulty meeting desired performance or scalability targets, especially when concentrated in a certain feature subsystem&lt;br /&gt;&lt;li&gt;adding new instances of a certain class of feature does not get easier over time&lt;br /&gt;&lt;li&gt;lots of bugs being generated by a specific subsystem&lt;br /&gt;&lt;li&gt;increased time-to-market for new features&lt;br /&gt;&lt;li&gt;accleration of bug creation rates&lt;br /&gt;&lt;li&gt;accrual of standard operational processes that require manual intervention/support&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;So when you have some or (gulp) all of those symptoms, you probably have architectural debt lurking in your system. Once you have identified it and have a new target architecture, a lot of this will get converted into transitional debt while you are making the changes.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Technical debt vs. technical investment&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;I want to be careful here to distinguish between two sorts of non-functional requirements that might show up on a "tech backlog":&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;technical debt&lt;/b&gt;: this is current brokenness or unneeded complexity in the system that is actively slowing down the business of turning product backlog into working software for your customer.&lt;br /&gt;&lt;li&gt;&lt;b&gt;technical investment&lt;/b&gt;: these are things that are not necessarily broken &lt;em&gt;per se&lt;/em&gt;, but which could speed things up for someone. A good example of this would be automating a manual process.&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Technical investments can probably be put off while you have existing technical debt, although it can sometimes be hard to distinguish between the two. Clear technical &lt;em&gt;debt&lt;/em&gt; should probably be prioritized at the top of a product backlog, unless there are really high ROI items that might trump it. In general, getting rid of technical debt will increase the ROI of everything else on the backlog, simply by decreasing the "I" part. It can also make estimation more accurate by reducing the complexity of the system to which new functionality will be added. &lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Whose responsibility is technical debt?&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Generally, as the folks with the technical ability to recognize it, it is the development team's responsibility to try to avoid accruing technical debt while producing product. Failing that, it is their responsibility to recognize/document existing debt and to advocate for its removal. However, note that there are often symptoms of technical debt, such as those I've listed above for architectural debt, that can be recognized by non-technical folks too.&lt;br /&gt;&lt;br /&gt;On the flip side, business folks / product owners need to be able to trade off short term wins that accrue technical debt vs. taking longer to produce a product with less debt. Communication with the tech team is of vital importance here; undoubtedly there will be times when a short-term win will be important (especially with a first-to-market situation), but it needs to be accompanied by a plan to eliminate the accrued debt. i.e. Treat your technical debt like credit card debt that should be paid down ASAP, and not as a long-term mortgage.&lt;br /&gt;&lt;br /&gt;The interest on your technical debt is probably &lt;em&gt;not&lt;/em&gt; tax-deductible.&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-6241208165467935170?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/GRKF6PgqL9DiEqDBwG4f2CKFm3s/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/GRKF6PgqL9DiEqDBwG4f2CKFm3s/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/GRKF6PgqL9DiEqDBwG4f2CKFm3s/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/GRKF6PgqL9DiEqDBwG4f2CKFm3s/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/6241208165467935170/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=6241208165467935170" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/6241208165467935170?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/6241208165467935170?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/LpfpEWKiOCQ/cracking-down-on-technical-debt.html" title="Cracking down on technical debt" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2008/08/cracking-down-on-technical-debt.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0UESH8-fyp7ImA9WxZbFUs.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-6811819741886821371</id><published>2008-04-18T19:11:00.004-04:00</published><updated>2008-04-18T19:26:49.157-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-04-18T19:26:49.157-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="product owner" /><category scheme="http://www.blogger.com/atom/ns#" term="timeboxing" /><category scheme="http://www.blogger.com/atom/ns#" term="teams" /><category scheme="http://www.blogger.com/atom/ns#" term="prioritization" /><category scheme="http://www.blogger.com/atom/ns#" term="negotiation" /><category scheme="http://www.blogger.com/atom/ns#" term="scrum" /><title>The Crucible</title><content type="html">We've recently re-adjusted our sprint length to be 3 weeks, down from 4. The reasoning behind this was to allow us to align with sprints on other products, to give us a uniformity of scheduling and to permit inter-product developer swaps from sprint to sprint, if needed.&lt;br /&gt;&lt;br /&gt;One of the side effects is that our planning process now takes up proportionately more time of the sprint and so the actual work time of the sprint is quite compressed. Couple this with a handful of developers being out for personal reasons (wedding, health issues, impending birth of a child) and suddenly this sprint had a lot of high priority sprint backlog and not a lot of available story point capacity on the teams.&lt;br /&gt;&lt;br /&gt;But then something magical happened--what I'll call the "crucible moment." The status of the sprint was that there were a bunch of high priority, smaller maintenance tasks (cleanup of existing features, releasing the previous sprints' work to production, etc.) and then one big new feature. As the teams were working their way down the list of user stories and filling up their story points, everyone quickly realized that that big new feature might not fit into the sprint.&lt;br /&gt;&lt;br /&gt;With the purifying fire of timeboxing (forgive my overly dramatic metaphors here), the sprint teams and product teams immediately began self-organizing and negotiating. No one wanted to have a sprint without a killer feature, and so the horse trading began. Some of the higher priority stories were off the "tech backlog" -- non-functional investments in build infrastructure, etc. Developers began identifying tech backlog items that could stand to wait. Product owners began reconsidering some of the higher priority smaller stuff, conceding that some of them might not be so important after all. Different development teams tried to juggle stories between themselves so that the large story could get onto one team's sprint backlog (we prefer not to split stories across teams, to minimize cross-team dependencies). Some stories were scoped down to take less time.&lt;br /&gt;&lt;br /&gt;And then, when it was all done, we discovered there was enough room for &lt;b&gt;two&lt;/b&gt; pretty big features.&lt;br /&gt;&lt;br /&gt;This was scrum at its best--focusing on the art of the possible to squeeze as much as possible into a fixed amount of time. No time for extraneous explorations, just a focus on extracting the maximum product value out of the available resources, with a brutal willingness to critically re-examine priorities. What's more, it was carried out in a context of teamwork between the product owners and the teams; &lt;b&gt;everyone&lt;/b&gt; was trying to figure out how to get those features in.&lt;br /&gt;&lt;br /&gt;So, a great day for our product and for our organization. We might actually be getting the hang of this scrum thing....&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-6811819741886821371?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/UIbhi4Xf2VWvWh_oF-0bSGDKnOI/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/UIbhi4Xf2VWvWh_oF-0bSGDKnOI/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/UIbhi4Xf2VWvWh_oF-0bSGDKnOI/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/UIbhi4Xf2VWvWh_oF-0bSGDKnOI/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/6811819741886821371/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=6811819741886821371" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/6811819741886821371?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/6811819741886821371?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/dNW4LHCym30/crucible.html" title="The Crucible" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2008/04/crucible.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A08MQXY6cCp7ImA9WxZVFUU.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-8090721625848513962</id><published>2008-03-26T23:14:00.003-04:00</published><updated>2008-03-26T23:51:20.818-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-26T23:51:20.818-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="timeboxing" /><category scheme="http://www.blogger.com/atom/ns#" term="teams" /><category scheme="http://www.blogger.com/atom/ns#" term="sprint review" /><category scheme="http://www.blogger.com/atom/ns#" term="burndown" /><category scheme="http://www.blogger.com/atom/ns#" term="scrum master" /><category scheme="http://www.blogger.com/atom/ns#" term="estimation" /><category scheme="http://www.blogger.com/atom/ns#" term="scrum" /><title>Sprint Review</title><content type="html">Just wanted to follow up on my &lt;a href="http://codeartisan.blogspot.com/2008/02/back-to-basics.html"&gt;previous post&lt;/a&gt; about how we were going to get back to scrum basics this sprint. We had our sprint review and sprint retrospectives today, and the sprint was viewed as a great success.&lt;br /&gt;&lt;br /&gt;Here are some things that I think contributed to our success:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Better estimation&lt;/b&gt;: We used past task estimation history plus monte-carlo simulation, as I &lt;a href="http://codeartisan.blogspot.com/2007/12/scrum-thoughts-improving-individual.html"&gt;described earlier&lt;/a&gt;, to sign up for a set of achievable tasks. The end result was that we showed up at the review with 90-95% of the committed work done. The parts that didn't get done were due to external blockers we couldn't do anything about (e.g. external partner didn't have a data feed ready) or were due to too many round-trips through the design-IA-product-eng collaboration cycle (essentially, deviating from the plan we set forth at the beginning of the sprint) that took time away from other things.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Commitment&lt;/b&gt;: Towards the end of the set of product backlog our team was planning, we hit a fairly large task that was hard to estimate out. However, we had two team members who were very lightly booked, and we simply asked them if they were willing to commit to finishing the task somehow by the end of the sprint. They signed up, and they got it done. There was really no talk of punting things off the sprint, although there were a couple of rescopings and/or a change of plan due to time constraints that happened to get things done by the review. In general, I think these changes actually just meant the abstract functionality in the user story got built with fewer resources -- i.e. we completed them more efficiently thanks to the pressure of timeboxing.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Empowerment&lt;/b&gt;: Thanks to being committed to their tasks, my teammates basically kept themselves unblocked most of the sprint by engaging with other departments (design, product, IA, ops, QA) early on to make sure they could get what they needed. Most of the time things stayed unblocked by getting the right folks together in one room. One of my teammates mentioned that one of the things I was best able to do was to simply identify which people needed to be in the room! Then he went off and arranged the meetings and got things done himself.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Shippable product&lt;/b&gt;: We planned explicit tasks for each feature to test/verify it, to show QA how it worked, and to demo it and get explicit signoff from the product owner. Our product owner gave me the feedback that this resulted in most of the user stories we called "done" being potentially shippable.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Self-organization&lt;/b&gt;: The team dynamically swapped tasks with one another to load balance effectively during the sprint. There was such appreciation for this help that people started buying each other donuts as thank-yous, and I think we ended up with around 4 dozen donuts being exchanged by the end of the sprint. Bad for the waistline, but good for team morale!&lt;br /&gt;&lt;br /&gt;On another note, this was a re-entry for me into the dual role of scrum master and team member (in that I signed up for development sprint tasks as well as being scrum master). For other developers who find themselves being called on to carry the scrum master mantle, here are some tips to help you survive:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;Only book yourself half-time&lt;/b&gt;. The rest of the time will be eaten up by scrum mastering, and if you don't scale back on your coding commitments, you're just going to end up staying up late working all sprint.&lt;br /&gt;&lt;li&gt;&lt;b&gt;Tell people how to get unblocked rather than trying to unblock them yourself&lt;/b&gt;. This seems pretty simple, but is a big time saver. For one thing, it's probably quicker to describe what to do (e.g. just set up a meeting with X, Y, and Z) than it is for you to send out the meeting invite yourself. Plus, you remove &lt;em&gt;yourself&lt;/em&gt; as a bottleneck -- so your teammate doesn't have to wait for you to send the invite. Furthermore, after you do this a few times, your teammates will pick it up and will be able to just solve more of their problems themselves without asking you for help.&lt;br /&gt;&lt;li&gt;&lt;b&gt;Get good reports&lt;/b&gt;. I invested 5-6 hours towards the beginning of the sprint to get some automated reports set up to get info out of our ticketing system. I really ended up just using two reports:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Storyboard: for each story, identify who has tickets against it, and identify whether each ticket is "Not Started", "In Progress", or "Finished". This provides a quick way to scan the status of each story and reminds you to ask questions like: what do we need to do to close out that story again? why are we working on lower priority things instead of the higher priority ones? etc.&lt;br /&gt;&lt;li&gt;Burndown count: for each story, add up time remaining on each ticket, and then provide a total for the amount of hours left for the sprint.&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;So what I did was: right before scrum, I pulled up the Storyboard report to check status so I could ask followup questions during scrum. Right after scrum, I hit the burndown count and then actually plotted by hand on a big piece of flipchart paper the burndown graph. That was pretty much the extent of the reporting I needed.&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;So, all in all, things turned out pretty well. I'm excited to see the teams back in a good sprint cycle groove and turning things out (our review meeting lasted almost 5 hours, mainly because so much had gotten done across the multiple teams working on the product that it took a while to go over it all). We'll be mostly keeping things the same going into the next sprint, so I'll be curious to see if the teams are able to accelerate now that they're used to working this way.&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-8090721625848513962?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/26u_1Yx94EkF8jl57Ou0cW5b2Qs/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/26u_1Yx94EkF8jl57Ou0cW5b2Qs/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/26u_1Yx94EkF8jl57Ou0cW5b2Qs/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/26u_1Yx94EkF8jl57Ou0cW5b2Qs/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/8090721625848513962/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=8090721625848513962" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/8090721625848513962?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/8090721625848513962?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/Zz1ORsET6CQ/sprint-review.html" title="Sprint Review" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2008/03/sprint-review.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEQHRXg9fSp7ImA9WxRSGEU.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-7730707034265885877</id><published>2008-03-16T22:12:00.006-04:00</published><updated>2008-09-19T23:45:34.665-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-09-19T23:45:34.665-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="rest" /><category scheme="http://www.blogger.com/atom/ns#" term="design philosophy" /><category scheme="http://www.blogger.com/atom/ns#" term="web2.0" /><category scheme="http://www.blogger.com/atom/ns#" term="microservices" /><category scheme="http://www.blogger.com/atom/ns#" term="esr" /><category scheme="http://www.blogger.com/atom/ns#" term="unix" /><category scheme="http://www.blogger.com/atom/ns#" term="architecture" /><category scheme="http://www.blogger.com/atom/ns#" term="rest programming" /><title>REST: Unix programming for the Web</title><content type="html">I've been giving some thought to &lt;a href="http://en.wikipedia.org/wiki/Representational_State_Transfer"&gt;REST-style architectures&lt;/a&gt; recently, and recently re-read some of &lt;a href="http://www.catb.org/~esr/writings/taoup/"&gt;The Art of Unix Programming&lt;/a&gt; by &lt;a href="http://www.catb.org/~esr/"&gt;Eric S. Raymond&lt;/a&gt;. ESR notes that some of the characteristics of &lt;a href="http://en.wikipedia.org/wiki/Unix_philosophy"&gt;Unix-style programming&lt;/a&gt; include:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Do one thing, and do it well (attributed to &lt;a href="http://en.wikipedia.org/wiki/Douglas_McIlroy"&gt;Doug McIlroy&lt;/a&gt;) &lt;/li&gt;&lt;br /&gt;&lt;li&gt;Everything is a file.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Comprise complex systems by connecting smaller, simpler programs (e.g. Unix pipes).&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Unix-style systems have had undeniable success and remarkable stickiness for a technology; I have an old copy of my father's System V manual from when he worked at Bell Labs (yeah, they actually &lt;i&gt;printed&lt;/i&gt; out the man pages and bound them!), and I pretty much recognize everything in there. Sure, there are many new commands available, new kernels, new distributions, etc., but they are all very recognizably &lt;i&gt;Unixy&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;I've been thinking about how this philosophy applies to the Web 2.0 world. I think this list turns into:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Do one thing, and do it well.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Everything is a RESTful service.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Comprise complex systems by interconnecting smaller, simpler services.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;For one thing, "do one thing, and do it well" isn't limited to Unix, this is a key part of abstracting and decomposing a technical problem. But we see it everywhere: I read my mail on &lt;a href="http://mail.google.com/"&gt;Gmail&lt;/a&gt;, keep my to-do lists on &lt;a href="http://www.rememberthemilk.com/"&gt;Remember The Milk&lt;/a&gt;, store photos on &lt;a href="http://www.flickr.com/"&gt;Flickr&lt;/a&gt;, keep my browser bookmarks on &lt;a href="http://del.icio.us/"&gt;del.icio.us&lt;/a&gt;, etc. All these sites adhere to this principle.&lt;br /&gt;&lt;br /&gt;But taking things down a layer, what does this mean to someone architecting or implementing a Web 2.0 service? For one thing, I think this means that it would make sense to break your overall service down into individual &lt;i&gt;microservices&lt;/i&gt; that are individually maintained and deployed; i.e. break things down to the smallest level where they still make coherent sense.&lt;br /&gt;&lt;br /&gt;As for "everything is a REST service", the everything-is-a-file abstraction worked for Unix because there was a small set of common operations that applied to files (&lt;tt&gt;open&lt;/tt&gt;, &lt;tt&gt;close&lt;/tt&gt;, &lt;tt&gt;dup&lt;/tt&gt;, &lt;tt&gt;read&lt;/tt&gt;, &lt;tt&gt;write&lt;/tt&gt;). Sounds a lot like doing REST over HTTP, with HEAD, GET, PUT, POST, DELETE.&lt;br /&gt;&lt;br /&gt;Combining various small REST microservices into larger services is already being done (this is, after all, basically what &lt;a href="http://www.google.com/ig"&gt;iGoogle&lt;/a&gt; is) on a one-off basis. The main question is: &lt;b&gt;what is the Web 2.0 equivalent of the pipe?&lt;/b&gt; Namely, is there an easily understood abstraction for composing webservices? Sounds like a topic that might be rife for some kind of logical calculus (like the &lt;a href="http://en.wikipedia.org/wiki/Relational_calculus"&gt;relational calculus&lt;/a&gt; for databases or the &lt;a href="http://en.wikipedia.org/wiki/Pi_calculus"&gt;pi-calculus&lt;/a&gt; for concurrent processes), e.g. the &lt;em&gt;REST calculus&lt;/em&gt;. If there were a couple of easily understand and specifiable combinators, these could be pretty easily built into some language-specific libraries for use in quickly building some new macro-services.&lt;br /&gt;&lt;br /&gt;I might try to interest some of my old colleagues from my programming language research days to see if they have anything to say about the matter....&lt;br /&gt;&lt;br /&gt;Comments definitely welcome here. Is this a new idea? Are others espousing this? Is this even a good idea?&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-7730707034265885877?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/hnPJmhaucMWtTWPCqHgDdVVOPKg/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/hnPJmhaucMWtTWPCqHgDdVVOPKg/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/hnPJmhaucMWtTWPCqHgDdVVOPKg/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/hnPJmhaucMWtTWPCqHgDdVVOPKg/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/7730707034265885877/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=7730707034265885877" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/7730707034265885877?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/7730707034265885877?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/RNwUxDzsHk4/rest-unix-programming-for-web.html" title="REST: Unix programming for the Web" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">6</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2008/03/rest-unix-programming-for-web.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEcMQXY-fip7ImA9WxZXEks.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-1646420855191129533</id><published>2008-02-28T23:53:00.003-05:00</published><updated>2008-02-29T00:14:40.856-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-29T00:14:40.856-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="INVEST" /><category scheme="http://www.blogger.com/atom/ns#" term="pre-planning" /><category scheme="http://www.blogger.com/atom/ns#" term="user stories" /><category scheme="http://www.blogger.com/atom/ns#" term="scrum" /><title>Return on INVESTment</title><content type="html">At our last &lt;a href="http://codeartisan.blogspot.com/2007/10/scrum-thoughts-pre-planning.html"&gt;pre-planning&lt;/a&gt; meeting, we made a point of putting all the user stories into &lt;a href="http://codeartisan.blogspot.com/2008/02/investing-in-user-stories.html"&gt;INVEST&lt;/a&gt; format, and I was pleased that there was a general consensus that this worked well.&lt;br /&gt;&lt;br /&gt;I think this took quite a while, partially because we were all getting used to evaluating the statement of the stories critically, but I think this was worth it. We had product, IA, and engineering folks suggesting wordings for stories, and at least some of our stories came in in a canonical format:&lt;br /&gt;&lt;br /&gt;"As &amp;lt;who&amp;gt;, I want &amp;lt;feature&amp;gt;, so that &amp;lt;value&amp;gt;."&lt;br /&gt;&lt;br /&gt;Having the full INVEST filter did help us with a few things:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;Independent:&lt;/b&gt; We did not have a lot of stories that were dependent, but we did end up with a very few that were cross-team. Since this was just a handful of the stories, we figured it would be ok to leave the story as is (since we couldn't quickly come up with a way to rewrite it) and track the dependencies through our &lt;a href="http://codeartisan.blogspot.com/2007/10/scrum-thoughts-scrums-and-scrums-of.html"&gt;scrum-of-scrums&lt;/a&gt;.&lt;br /&gt;&lt;li&gt;&lt;span style="font-weight:bold;"&gt;Negotiable:&lt;/span&gt; I think we generally nailed this one. We did not look at any IA wireframes or Design mockups during pre-planning, and were able to identify the high-level goals here. This has already become useful in planning, where my team has suggested a new / faster approach to at least one story which was not as originally conceived, but our Product Owner agreed that we still hit the abstract requirement.&lt;br /&gt;&lt;li&gt;&lt;span style="font-weight:bold;"&gt;Valuable:&lt;/span&gt; There were a few stories that were original cast as abstract technical requirements (e.g. make the middleware support foo), but we refactored them in a way that expressed value to the end user (which, incidentally, will make it more obvious how to test).&lt;br /&gt;&lt;li&gt;&lt;span style="font-weight:bold;"&gt;Estimable:&lt;/span&gt; We've already agreed not to do a small handful of stories because we realized there were too many unknowns; for one of these, there was a middleware task that depended on IA we hadn't seen and on data we didn't have in the DB yet. We actually spent a lot of time trying to talk about how to tackle this before we realized we couldn't estimate it. Since it was not critical for implementation this sprint, we agreed with the product owner to turn this into a story where we would do a feasibility study and rough design by the end of the sprint instead, which was something we could commit to. (The value here is to the product owner, who will then be able to write an Estimable story about the feature!)&lt;br /&gt;&lt;li&gt;&lt;span style="font-weight:bold;"&gt;Small:&lt;/span&gt; We did a good job here of refactoring large stories into multiple pieces and then figuring out how to get Value out of each piece. We only had a handful of medium to large stories.&lt;br /&gt;&lt;li&gt;&lt;span style="font-weight:bold;"&gt;Testable:&lt;/span&gt; We're asking each team during planning to make sure they hash out "how to demo" for the feature with the Product Owner, so we should know if we've gotten there by the end of sprint planning.&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;With the up-front work on the stories we did, our team has found it easier to negotiate a specific solution and in some cases to actually plan without the product owner, which is actually convenient (he's doing multiple duty so he's having to bounce back and forth between multiple sprint planning sessions!).&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-1646420855191129533?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/D4-piCAhMN3Z_P9dSQseFc53gFM/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/D4-piCAhMN3Z_P9dSQseFc53gFM/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/D4-piCAhMN3Z_P9dSQseFc53gFM/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/D4-piCAhMN3Z_P9dSQseFc53gFM/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/1646420855191129533/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=1646420855191129533" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/1646420855191129533?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/1646420855191129533?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/kcf4okgIzs0/return-on-investment.html" title="Return on INVESTment" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2008/02/return-on-investment.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CU4MRno8cCp7ImA9WxZQGU8.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-4746015782966704588</id><published>2008-02-24T16:21:00.004-05:00</published><updated>2008-02-25T02:19:47.478-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-25T02:19:47.478-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="INVEST" /><category scheme="http://www.blogger.com/atom/ns#" term="product owner" /><category scheme="http://www.blogger.com/atom/ns#" term="scrum master" /><category scheme="http://www.blogger.com/atom/ns#" term="user stories" /><category scheme="http://www.blogger.com/atom/ns#" term="estimation" /><category scheme="http://www.blogger.com/atom/ns#" term="scrum" /><category scheme="http://www.blogger.com/atom/ns#" term="cross-team dependencies" /><title>INVESTing in user stories</title><content type="html">In my &lt;a href="http://codeartisan.blogspot.com/2008/02/back-to-basics.html"&gt;previous post&lt;/a&gt; I made reference to the &lt;a href="http://abrachan.wordpress.com/2007/02/10/invest-an-acronymn-for-getting-the-requirements-right/"&gt;INVEST&lt;/a&gt; acronym for evaluating user stories:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;(I)ndependent&lt;/b&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;(N)egotiable&lt;/b&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;(V)aluable&lt;/b&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;(E)stimable&lt;/b&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;(S)mall&lt;/b&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;(T)estable&lt;/b&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;I'd like to spend a little bit of time talking about each of these characteristics, and motivating why each is important through some anecdotes about what happens when each characteristic is not attained.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Independent&lt;/b&gt;: The idea here is that stories are free of dependencies from one another. (A good test would be to ask if you could implement them in any order, rather than just in the priority order generated by the Product Owner). We want this for multiple reasons: first, on a multi-team project, it allows stories to be load-balanced across teams more easily, since in theory any one story could get moved around, rather than having to move an entire batch of them. Secondly, and on a related note, it means that when sprint teams sign up for work, they can draw the commit line anywhere. Thirdly, it helps avoid cross-team dependencies (although a &lt;a href="http://codeartisan.blogspot.com/2007/10/scrum-thoughts-scrums-and-scrums-of.html"&gt;scrum-of-scrums&lt;/a&gt; and specific attention to dependent situations can handle it, it is still much easier to handle all your dependencies intra-team).&lt;br /&gt;&lt;br /&gt;For a specific example, consider if you have teams that are layered horizontally by functional layer (e.g. a database team, and a webapp team). If you have two stories which are the "halves" of building some functionality, and assign one to each team, you take on the following risks:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;teams must coordinate closely around this feature (extra communication/tracking overhead)&lt;br /&gt;&lt;li&gt;if one team finishes but the other doesn't, you may have extra "clean up" work at the end of the sprint to make the software still work, and you may have had a team do work that ultimately had no product impact that sprint (e.g. middleware can handle some new data and display it, but data didn't actually apppear in the DB, so no actual behavioral difference)&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;We'll see that some of the other INVEST characteristics actually help track this down as well.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Negotiable.&lt;/b&gt; To me, this means that the requirements given are as abstract as possible, so that the actual details of what is going to get built are determined by the team and Product Owner during &lt;a href="http://codeartisan.blogspot.com/2007/10/scrum-thoughts-sprint-planning.html"&gt;sprint planning&lt;/a&gt; and modified as needed over the course of the sprint. For example, "user can change timezone with one click" vs. "user timezone is shown in a 100x25 dropdown box in the header". (Of course, if the latter language actually represented a contractual obligation, then that might be as abstract as you can make it, but you get the idea...).&lt;br /&gt;&lt;br /&gt;This is primarily important for two reasons: to allow the team to exercise maximal creativity and to allow the team to adjust for the unpredictable events that will happen mid-sprint. In the former case, the dropdown box might be tacked on to the story as a starting point or suggestion, but the team may come up with an approach that is easier to implement or which actually satisfies the Product Owner more. In the latter case, it leaves some room to negotiate if the team has under-estimated and is behind mid-sprint, and still be able to "finish" the user story.&lt;br /&gt;&lt;br /&gt;The danger of not being able to negotiate the functionality is really twofold: first, not completing the full set of work in a sprint eventually crushes team morale; not being able to complete an assignment is a big downer, especially when you have worked hard and because, due to the inherent complexity of software development, things just took longer than you thought. Eventually you get to the point where your team will just shrug, and say, oh well, I guess we won't finish that stuff. I have seen this happen first-hand, and it is demotivating.&lt;br /&gt;&lt;br /&gt;Secondly, there is danger to the product roadmap. By not having requirements couched abstractly, you run the risk of slipping features or having unfinished, "carryover" work, which adds up and pushes the product roadmap back. By giving the team the freedom to brainstorm a way to satisfy the requirement in less time, you are not letting them off the hook -- you are using the deadline of the end of the sprint as pressure to encourage the team to develop the functionality as efficiently as possible. The resulting functionality may not be as complex or deep as originally hoped for or conceived, but if the spirit of the story is met, then you have some aspect of it ready to go out as shippable product.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Valuable.&lt;/b&gt; Each user story should provide value somehow. The original articles I read about this amended this to "provide value to the end user / customer." While I think noting the value explicitly can help product owners check off that the story will help their Key Performance Indicators (KPI), I think the more important thing this brings is that the &lt;em&gt;team&lt;/em&gt; is aware of &lt;em&gt;why&lt;/em&gt; this story is important. This can help constrain the solution space for Negotiation in an important way. Finally, this is just a cross-check for Indepencence; if this story is completed, and no other, does it generate value, or do we also need another story to be finished in order to get the value?&lt;br /&gt;&lt;br /&gt;On a side note, the phrase "to the end user" is an interesting one. We keep a "tech backlog" of infrastructure/refactoring ideas around, and prior to each sprint, we evaluate which ones are critical to current development, and ask for those to be prioritized in with the current product backlog. Generally, we have been very judicious about this, maybe in a nod to providing direct benefit to an end user -- we usually wait until an infrastructure adjustment is needed or desirable for implementing new functionality before taking it on. This suggests that other infrastructure work get carried out during lab days, to scratch those developers' itches. I'm still up in the air over how strict we should be about "to the end user", but I do lean towards requiring that most of the time.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Estimable.&lt;/b&gt; If you can't put an estimate on it, either the requirements are too vague (see Testable), or the technical solution is unknown (e.g. "I don't even know if this is feasible!"). In the latter case, a suggested tactic would be to alter the user story into a feasibility study / research effort for this sprint, which could be easily timeboxed; the original story could then be revisited in a later sprint, when there will be less cloudiness around it, and it can be properly estimated.&lt;br /&gt;&lt;br /&gt;Where this can bite you is in signing up for work you don't know you can finish. This sets up for an expectation mismatch with your Product Owner, and also prevents you from making efficient use of your time; you run the risk of attempting to estimate it, and either grossly underestimating it, putting all the lower-priority sprint backlog at risk, of grossly overestimating it and not signing up for enough work, or of padding the estimate to try to account for the risk and then spending more time on it than the feature is really worth.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Small.&lt;/b&gt; We want things that will take no more than a full sprint to do, and hopefully less. While the full scope of a feature vision may require more than one sprint, you want to refactor it somehow so that you get &lt;em&gt;something&lt;/em&gt; out of even the very first sprint (see Valuable); then you can be sure that you reap some result from your effort in shippable product, as opposed to doing some partial work, and then having the remaining work deprioritized for several months before you can extract value.&lt;br /&gt;&lt;br /&gt;Finally, a purely pragmatic reason for keeping the stories small is to reduce the risk of gross under-estimation. I've personally been way off on a single big story (as everyone is from time to time), and when it's been a big one, I've simultaneously trashed my personal life for a month of overtime and sleep deprivation, while requiring a bunch of load-balancing and rejuggling across multiple sprint teams due to the fact that the rest of the sprint backlog I signed up for was now at risk. So don't do that -- take smaller bites, just like Mom used to say.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Testable&lt;/b&gt;. This primarily serves three purposes: ensuring that Product Owner expectations are in-tune with what the team thinks it is delivering (see also Negotiable), giving the team a way to know when it can stop working on the story, and giving the testers a starting point for writing their test cases. Beware of non-quantifiable adjectives like "good" or "acceptable" in your user story descriptions. For a while, when we were doing sprint planning on spreadsheets, we had a "How to Demo" column--this worked great while we did it, but we never had enough discipline to follow through here and continue doing it. This is one of the things I'm hoping to bring back during my &lt;a href="http://codeartisan.blogspot.com/2008/02/back-to-basics.html"&gt;Scrum revival&lt;/a&gt; next sprint.&lt;br /&gt;&lt;br /&gt;There's nothing worse than showing up to a sprint review and having your Product Owner say, "but that's not at all what I asked for, or that's not what I meant." Big morale crusher for everyone involved (team and stakeholders).&lt;br /&gt;&lt;br /&gt;Finally, this gives the Scrum master a hook to save the team from perfectionism or unbounded creativity. For example, if you've gotten the feature to the point where it satisfies the acceptance tests and has been built up to your standards of quality, just stop working on it, and start working on the next user story. This is your old friend, the Pareto principle, at work -- would you rather spend a day mining the long tail of a functionally complete feature, or would you rather spend it getting the up-front meat of a new feature? The other place this helps is when you finish a feature, and you and the Product Owner are looking at it, and you now see something totally awesome that is now possible -- stop, ship the feature you have, and queue the good idea up as a user story for the next sprint so it can be properly prioritized with everything else. Again, this is about efficient use of the time in the sprint.&lt;br /&gt;&lt;br /&gt;&lt;hr/&gt;&lt;br /&gt;&lt;br /&gt;I'm anticipating having this be a little painful as we work through this together with the product team the first time; we've all signed off on this in principle, but we've never actually attempted to make each story adhere to INVEST. I suspect, like all similar things, it will be a bit robotic for the first few stories, but we'll quickly get the hang of it and be able to move on during &lt;a href="http://codeartisan.blogspot.com/2007/10/scrum-thoughts-pre-planning.html"&gt;pre-planning&lt;/a&gt;. I'll let you know how it goes.&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-4746015782966704588?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/V_us3nf091nq6EoEm9DKpXWt_wY/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/V_us3nf091nq6EoEm9DKpXWt_wY/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/V_us3nf091nq6EoEm9DKpXWt_wY/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/V_us3nf091nq6EoEm9DKpXWt_wY/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/4746015782966704588/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=4746015782966704588" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/4746015782966704588?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/4746015782966704588?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/a0_WCMnCCCE/investing-in-user-stories.html" title="INVESTing in user stories" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2008/02/investing-in-user-stories.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEAMRX4_cCp7ImA9WxZQGU0.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-1637374477522471655</id><published>2008-02-23T12:59:00.003-05:00</published><updated>2008-02-24T21:33:04.048-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-24T21:33:04.048-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="INVEST" /><category scheme="http://www.blogger.com/atom/ns#" term="user stories" /><category scheme="http://www.blogger.com/atom/ns#" term="sprint goal" /><category scheme="http://www.blogger.com/atom/ns#" term="scrum" /><title>Back to Basics</title><content type="html">&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://farm2.static.flickr.com/1090/1299464120_24651a3f50_b.jpg"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 200px;" src="http://farm2.static.flickr.com/1090/1299464120_24651a3f50_b.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I've been re-reading &lt;a href="http://www.amazon.com/Agile-Software-Development-SCRUM-Schwaber/dp/0130676349/ref=pd_bbs_sr_1?ie=UTF8&amp;s=books&amp;qid=1203789753&amp;sr=1-1"&gt;"Agile Software Development with Scrum"&lt;/a&gt; to see if it has any insight for some of our current product struggles. Fortunately, I think it does, and I've been getting fired up for a great Scrum revival. [Revival tent picture courtesy of &lt;a href="http://www.flickr.com/photos/pinkmoose/"&gt;PinkMoose&lt;/a&gt; on &lt;a href="http://www.flickr.com/"&gt;Flickr&lt;/a&gt;, used under a &lt;a href="http://creativecommons.org/licenses/by/2.0/deed.en"&gt;Creative Commons Attribution license&lt;/a&gt;.] I'm going to be reprising my role as Scrum Master for my team next sprint as well, so hopefully I'll be able to transmit some of these values.&lt;br /&gt;&lt;br /&gt;Looking forward to getting back to some Scrum fundamentals, including:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;sprint goal:&lt;/b&gt; let's set an overarching sprint goal we can be working towards&lt;br /&gt;&lt;li&gt;&lt;b&gt;&lt;a href="http://agilesoftwaredevelopment.com/blog/vaibhav/good-user-story-invest"&gt;INVEST&lt;/a&gt; user stories&lt;/b&gt;: in particular, the "N" -- negotiable -- means we have maximum flexibility to react with agility during the sprint&lt;br /&gt;&lt;li&gt;&lt;b&gt;commitment:&lt;/b&gt; the team signs up for a set of work, and commits to &lt;em&gt;completing&lt;/em&gt; it by the end of the sprint. No carryover. If burndown shows we are behind, we need to collaborate with the Product Owner to revise functionality / approach so that we have an achievable set of work. Also, we need to have the team take responsibility for completing the work -- no more reporting blockers and then giving up without trying something else.&lt;br /&gt;&lt;li&gt;&lt;b&gt;product increment&lt;/b&gt;: end of the sprint, we have a potentially shippable product. No half-baked / half-finished pages on the site.&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;I'm pretty excited for the upcoming sprint. We've got a lot of talented folks, we just need to really get them motivated and self-organized, and then awesome stuff will erupt.&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-1637374477522471655?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/qMbEYllX5JxgA3kne5hhdpyppUY/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/qMbEYllX5JxgA3kne5hhdpyppUY/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/qMbEYllX5JxgA3kne5hhdpyppUY/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/qMbEYllX5JxgA3kne5hhdpyppUY/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/1637374477522471655/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=1637374477522471655" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/1637374477522471655?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/1637374477522471655?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/_IGHtTGRNPc/back-to-basics.html" title="Back to Basics" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2008/02/back-to-basics.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEAASH84eSp7ImA9WxZQGU0.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-2554032136230808986</id><published>2008-02-13T21:21:00.003-05:00</published><updated>2008-02-24T21:32:29.131-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-24T21:32:29.131-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="REST API" /><category scheme="http://www.blogger.com/atom/ns#" term="CRUD" /><title>REST-based service design, v2</title><content type="html">I want to revisit the basic "favorite food" service from last post, in light of some further discussion I've had with colleagues.&lt;br /&gt;&lt;br /&gt;&lt;dl&gt;&lt;br /&gt;&lt;dt&gt;&lt;tt&gt;http://host/path/{userid}/favorites&lt;/tt&gt;&lt;br /&gt;&lt;dd&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;GET:&lt;/b&gt; returns a list of the user's favorite foods, in some representation. Returns 404 NOT FOUND if the user has never specified favorites.&lt;br /&gt;&lt;li&gt;&lt;b&gt;PUT:&lt;/b&gt; sets the list of the user's favorite foods to be the value sent, creating it if necessary.&lt;br /&gt;&lt;li&gt;&lt;b&gt;DELETE:&lt;/b&gt; removes the user's favorite list. This is different than doing a PUT with an empty list.&lt;br /&gt;&lt;li&gt;&lt;b&gt;POST:&lt;/b&gt; returns 405 METHOD NOT ALLOWED.&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;/dd&gt;&lt;br /&gt;&lt;br /&gt;&lt;dt&gt;&lt;tt&gt;http://host/path/{userid}/favorites/{food}&lt;/tt&gt;&lt;br /&gt;&lt;dd&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;GET:&lt;/b&gt; returns 200 OK if the food is a favorite of the user. Returns 404 NOT FOUND if the food is not a favorite of the user.&lt;br /&gt;&lt;li&gt;&lt;b&gt;PUT:&lt;/b&gt; makes the food one of the user's favorites&lt;br /&gt;&lt;li&gt;&lt;b&gt;DELETE:&lt;/b&gt; makes the food *not* a favorite&lt;br /&gt;&lt;li&gt;&lt;b&gt;POST:&lt;/b&gt; returns 405 METHOD NOT ALLOWED&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;/dd&gt;&lt;br /&gt;&lt;/dl&gt;&lt;br /&gt;&lt;br /&gt;So, PUT can create a resource if it doesn't exist, and DELETE of a URI means the next GET of it (in the absence of any other operations) will be a 404 NOT FOUND.&lt;br /&gt;&lt;br /&gt;One note is that as a client of this API, I want and can make use of the most atomic operation available. For example, to make "spaghetti" a favorite food, I could either:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;GET http://host/path/{userid}/favorites&lt;br /&gt;&lt;li&gt;PUT http://host/path/{userid}/favorites (new list with spaghetti added in)&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;or I could just:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;PUT http://host/path/{userid}/favorites/spaghetti&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;Note that in the first case, I might have an atomicity issue in the presence of concurrent access, so I might need to build in some sort of optimistic locking protocol, where the representation of the favorites list has a version number on it that is checked by the PUT operation. However, if I just use the second method, I don't have this issue, because the server handles all my concurrency/transactional stuff for me.&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-2554032136230808986?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/qCq2UpjOlPIThi5CGCHmpR3_t_Q/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/qCq2UpjOlPIThi5CGCHmpR3_t_Q/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/qCq2UpjOlPIThi5CGCHmpR3_t_Q/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/qCq2UpjOlPIThi5CGCHmpR3_t_Q/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/2554032136230808986/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=2554032136230808986" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/2554032136230808986?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/2554032136230808986?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/MYwok2QTzkA/rest-based-service-design-v2.html" title="REST-based service design, v2" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2008/02/rest-based-service-design-v2.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEAGSHc4fSp7ImA9WxZQGU0.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-8433082879428308325</id><published>2008-02-12T22:30:00.004-05:00</published><updated>2008-02-24T21:32:09.935-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-24T21:32:09.935-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="REST API" /><title>REST-based service design</title><content type="html">Just wanted to walk through a quick REST-style API design exercise. Let's suppose I want a service that lets folks maintain a list of favorite foods. Just as a quick strawman design, let's walk through some URIs and supported operations:&lt;br /&gt;&lt;br /&gt;&lt;dl&gt;&lt;br /&gt;&lt;dt&gt;&lt;tt&gt;http://host:port/path/{userid}/favorites&lt;/tt&gt;&lt;/dt&gt;&lt;br /&gt;&lt;dd&gt;GET on this URI returns a list of the current user's favorites. List might be empty. PUT on this URI overwrites an existing list of favorites. DELETE on this URI sets the list of favorites to the empty list.&lt;/dd&gt;&lt;br /&gt;&lt;br /&gt;&lt;dt&gt;&lt;tt&gt;http://host:port/path/{userid}/favorites/{food}&lt;/tt&gt;&lt;/dt&gt;&lt;br /&gt;&lt;dd&gt;GET on this URI returns something (really, we just want a 200 status code) if the food is a favorite, or returns 404 if it is not. DELETE on this URI un-favorites the food for the user. PUT on this URI makes the food a favorite.&lt;br /&gt;&lt;/dl&gt;&lt;br /&gt;&lt;br /&gt;So, questions for the audience:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Does this look reasonable?&lt;br /&gt;&lt;li&gt;Does the use of PUT look right, or would you use POST here?&lt;br /&gt;&lt;li&gt;How about the use of DELETE to set the full list to the empty list? Does it make sense to DELETE a URI and then be able to GET it and have something be there?&lt;br /&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-8433082879428308325?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/mSeYTOCS1WuWmfT4Jrr07xY8gNs/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/mSeYTOCS1WuWmfT4Jrr07xY8gNs/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/mSeYTOCS1WuWmfT4Jrr07xY8gNs/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/mSeYTOCS1WuWmfT4Jrr07xY8gNs/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/8433082879428308325/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=8433082879428308325" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/8433082879428308325?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/8433082879428308325?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/IXWPbmLGWAg/rest-based-service-design.html" title="REST-based service design" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2008/02/rest-based-service-design.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEAERXc5eip7ImA9WxZQGU0.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-6475454244820485033</id><published>2007-12-30T11:05:00.001-05:00</published><updated>2008-02-24T21:31:44.922-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-24T21:31:44.922-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="resolution" /><category scheme="http://www.blogger.com/atom/ns#" term="cobertura" /><category scheme="http://www.blogger.com/atom/ns#" term="tdd" /><category scheme="http://www.blogger.com/atom/ns#" term="javadoc" /><category scheme="http://www.blogger.com/atom/ns#" term="design patterns" /><category scheme="http://www.blogger.com/atom/ns#" term="estimation" /><category scheme="http://www.blogger.com/atom/ns#" term="unit test" /><title>Code Artisan's New Year's Resolutions</title><content type="html">Well, it's a new year, so time to sit down and draw up a list of development resolutions. Without further ado:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1. I will create unit tests for all my classes.&lt;/b&gt; There's no excuse not to have at least some kind of unit test for each class that gets created; this at the very least provides a stratum on which other people can flesh out the tests to cover things that come up. This may not be full-blown TDD, but we could at least enforce this with some kind of unit test coverage tool like Cobertura.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. When debugging a defect, the first step will be to write an automated test case that captures it.&lt;/b&gt; Nothing is more annoying than a bug that can't be reproduced. If you can actually get to the point where you can reliably trigger the bug, you're probably most of the way towards finding the problem. Furthermore, in the process, you'll have created an easy-to-run way to know when the bug is fixed. Since the test is automated, there's a good chance that test will get re-run a whole bunch more times in the future of the codebase, making sure that once a bug gets squashed, it stays squashed.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3. I will write Javadocs for all my public methods, except where they can be inherited from the Javadocs in an interface.&lt;/b&gt; This is just common courtesy to myself and the other developers. I'm cutting myself a break here, since one could argue that you really ought to document the &lt;tt&gt;protected&lt;/tt&gt; ones too, but we'll start small. This will also encourage definining interfaces and coding to them, as it saves a lot more effort if you can just Javadoc up the interface itself! For bonus points this year, write or find a tool to automatically check this.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;4. I will have explicit up-front design sessions before writing any code.&lt;/b&gt; I really don't know why we don't do this more than we do, other than we forget to do it. There are all kinds of good reasons to do this, like the fact that we &lt;em&gt;always&lt;/em&gt; come up with better designs in a small group than we do by ourselves, or that this will help all of us become better designers by being present while good designs are being born. Or that a good design is usually easier to implement, and with all the hard work I've already set out for myself, I don't mind finding resolutions that save me some effort.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;5. I will relentlessly learn to estimate better by considering my past estimating history when providing a new estimate.&lt;/b&gt; First step here is to actually look at my past history, so I know what it is! (Fortunately, we are already collecting the data for this...) Then possibly use techniques like those I laid out in a previous post about &lt;a href="http://codeartisan.blogspot.com/2007/12/scrum-thoughts-improving-individual.html"&gt;improving estimates&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Well, that's it. Those are some pretty specific resolutions, which, I'm given to understand, makes it more likely that they'll be adhered to. What are your coding resolutions for the new year?&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-6475454244820485033?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/weyUTtkTisPCaRupSB5SPIOR94k/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/weyUTtkTisPCaRupSB5SPIOR94k/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/weyUTtkTisPCaRupSB5SPIOR94k/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/weyUTtkTisPCaRupSB5SPIOR94k/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/6475454244820485033/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=6475454244820485033" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/6475454244820485033?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/6475454244820485033?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/-XsjctV0U8s/code-artisans-new-years-resolutions.html" title="Code Artisan's New Year's Resolutions" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2007/12/code-artisans-new-years-resolutions.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEECQ3s-eyp7ImA9WxZQGU0.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-4462157865994094605</id><published>2007-12-27T21:36:00.001-05:00</published><updated>2008-02-24T21:31:02.553-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-24T21:31:02.553-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="architecture" /><category scheme="http://www.blogger.com/atom/ns#" term="scrum" /><title>Who designed this stuff, anyway?</title><content type="html">Many development organizations have an "architect" role, meant to provide technical direction over an entire engineering process [As far as I can tell, "architect" really seems to be shorthand for "someone with deep and broad technical understanding and experience"]. How does this mesh with scrum-based, grass-roots development by empowered teams? Or perhaps, the real question is: if I have folks that would traditionally fill an architect role, what's the best way to utilize them in a scrum setting?&lt;br /&gt;&lt;br /&gt;Three areas in which these folks tend to contribute are (although this list is certainly not exhaustive):&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;R&amp;amp;D&lt;/b&gt;: advanced prototyping/proof-of-concept work&lt;br /&gt;&lt;li&gt;&lt;b&gt;Due Diligence&lt;/b&gt;: evaluation of emerging technology&lt;br /&gt;&lt;li&gt;&lt;b&gt;Design&lt;/b&gt;: participating in system and/or software architecture and design&lt;br /&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;Let's take each of these areas in turn.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;&lt;b&gt;R&amp;amp;D.&lt;/b&gt;&lt;/h3&gt; Here the notion is to invest some amount of exploratory effort to remove technical risk from a product or feature--to the point where the feasibility of an approach can be established and an associated rough LOE (level of effort) can be given for a real implementation using this approach. We typically hand this type of task off to an architect, hoping his/her broad technical background can help provide hints/leads to navigating the solution space, and thus reduce maximum uncertainty with a minimum of effort (bang for the buck!).&lt;br /&gt;&lt;br /&gt;Depending upon the nature of the problem at hand, and/or the structure of the available scrum teams, there are two readily apparent approaches:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;&lt;em&gt;run an R&amp;amp;D scrum team&lt;/em&gt;; this makes sense when there are sufficient architects around to form a team of them, and when the explorations are not directly tied to an existing product (e.g. technical feasibility for a new product)&lt;br /&gt;&lt;li&gt;&lt;em&gt;embed your architects on the scrum teams themselves&lt;/em&gt;: this makes sense when your architects are more scarce (because it will be beneficial to have them on the teams for the other reasons we'll mention below), but also when the explorations are directly related to an existing product. In this case, the rest of the scrum team they are on will benefit directly from (and help participate in) the exploration--this learning process will make a later production implementation easier, and will furthermore help teach the other team members how to do this type of work. This is vitally important if we are short on architects and good ones are hard to hire--then we need to view these other folks not as "non-architects" but rather as "architects-to-be." This model is also easy to follow if you are already using the idea of &lt;a href="http://codeartisan.blogspot.com/2007/10/scrum-thoughts-staggering-sprint-teams.html"&gt;prototyping sprints&lt;/a&gt;.&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;&lt;b&gt;Technical Due Diligence.&lt;/b&gt;&lt;/h3&gt; Here, again, we are counting on our architect's experience to enable a thorough and accurate assessment of some technology. If there is already an "architecture scrum" running as described above, this might be a natural place to fit these efforts. However, in the absence of a dedicated group, it may fall on an architect that is embedded on a scrum team (ideally one working on a product that might be impacted by the technology in question), which would also be perfectly fine.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;&lt;b&gt;Design.&lt;/b&gt;&lt;/h3&gt; Architects' experience helps most during design exercises--knowing what kind of pitfalls to look for, what kind of generality to build in, and what kind of approaches/patterns are available and relevant. Design is also an area where collaboration is both possible and usually helpful (at least up to the amount of people that can fit comfortably around a whiteboard!). So, no doubt--we want our architects participating here in order to get good designs up front and hopefully minimize refactoring later.&lt;br /&gt;&lt;br /&gt;Again, I am going to argue for the &lt;em&gt;embedded&lt;/em&gt; approach here, as opposed to running an architecture scrum that spits out designs. I think the benefits here are overwhelming:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;by generating designs as they are needed for feature development, effort will not be spent building in generality that may never be exercised&lt;br /&gt;&lt;li&gt;by having the scrum team participate in the design, those familiar with the details of the existing system will have input, ensuring the design is complete&lt;br /&gt;&lt;li&gt;by having the team participate, they will learn to become better designers themselves (again, a key benefit if there are not enough architects to go around--and there probably won't &lt;em&gt;ever&lt;/em&gt; be, by the way--as the architecture will have to live and grow regardless of whether an architect is available to design it all).&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Archi-who?&lt;/h3&gt;&lt;br /&gt;Well, I seem to have convinced myself, anyway, that the right thing to do with your architects is to just put them on your scrum teams and let them have at it (Ken Schwaber would be so proud!). In other words, don't treat them any differently than you would any other engineer in your development organization (indeed, in our current organization, we have "architects" that do production development, and "engineers" that do software architecture, so we're probably not far from this anyway). The main benefits of this approach are:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;em&gt;relevance&lt;/em&gt; - by staying grounded in the day-to-day realities of creating products, our architects will be focused on real needs&lt;br /&gt;&lt;li&gt;&lt;em&gt;integration&lt;/em&gt; - by having architecture activities happen in the context of normal scrums, those activities will have a high likelihood of impacting production code&lt;br /&gt;&lt;li&gt;&lt;em&gt;collaboration&lt;/em&gt; - as a team member, there are no artificial barriers to communication, no "us" and "them" mentality between architecture and engineering&lt;br /&gt;&lt;li&gt;&lt;em&gt;resourcing&lt;/em&gt; - architects can leverage their teammates to provide extra horsepower towards architecture tasks&lt;br /&gt;&lt;li&gt;&lt;em&gt;learning&lt;/em&gt; - by having scrum &lt;b&gt;teams&lt;/b&gt; responsible for architecture tasks, experienced team members will teach less experienced teammates how to accomplish architecture-related tasks&lt;br /&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-4462157865994094605?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/BK19mjDnW2hn-8zUuXDJh7u-N7A/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/BK19mjDnW2hn-8zUuXDJh7u-N7A/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/BK19mjDnW2hn-8zUuXDJh7u-N7A/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/BK19mjDnW2hn-8zUuXDJh7u-N7A/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/4462157865994094605/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=4462157865994094605" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/4462157865994094605?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/4462157865994094605?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/hLfbRs1iUD0/who-designed-this-stuff-anyway.html" title="Who designed this stuff, anyway?" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2007/12/who-designed-this-stuff-anyway.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEEFRXg8eSp7ImA9WxZQGU0.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-1301941150014002696</id><published>2007-12-18T18:57:00.001-05:00</published><updated>2008-02-24T21:30:14.671-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-24T21:30:14.671-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="ken schwaber" /><category scheme="http://www.blogger.com/atom/ns#" term="pmd" /><category scheme="http://www.blogger.com/atom/ns#" term="cobertura" /><category scheme="http://www.blogger.com/atom/ns#" term="sashimi" /><category scheme="http://www.blogger.com/atom/ns#" term="javadoc" /><category scheme="http://www.blogger.com/atom/ns#" term="technical debt" /><category scheme="http://www.blogger.com/atom/ns#" term="maven" /><category scheme="http://www.blogger.com/atom/ns#" term="scrum" /><title>What's the APR on your technical debt?</title><content type="html">Today we're going to discuss &lt;span style="font-style:italic;"&gt;technical debt&lt;/span&gt;--what it is, what its impact is, and what to do about it.&lt;br /&gt;&lt;br /&gt;One of the key tenets of scrum and other agile development methodologies is the idea of producing production-ready increments of work at the end of each iteration. Ken Schwaber calls this "sashimi" in the &lt;a href="http://www.controlchaos.com/about/how.php"&gt;scrum framework&lt;/a&gt;--a slice of product. You may have also heard scrum practioners talk about the definition of "done",  i.e. what steps have to be completed before we can consider a user story satisfactorily completed, which may include steps like:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;code written!&lt;br /&gt;&lt;li&gt;unit tests written and passing, with a certain degree of code coverage&lt;br /&gt;&lt;li&gt;automated acceptance tests written and passing&lt;br /&gt;&lt;li&gt;code refactored&lt;br /&gt;&lt;li&gt;code documented/commented (e.g. javadoc)&lt;br /&gt;&lt;li&gt;product owner signoff&lt;br /&gt;&lt;li&gt;documentation updated (e.g. UML diagrams)&lt;br /&gt;&lt;li&gt;etc.&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;In general, the notion is that the definition of "done" should capture the degree of code quality the team and product owner mutually agree to.&lt;br /&gt;&lt;br /&gt;Now, anyone who has tried to do this has probably run into this (as we have): a sprint gets started, and at some point through the sprint, you realize that you're not going to be able to finish all the user stories you signed up for by the end of the sprint. Or at least not get them all the way "done". So, you yield to the temptation and cut corners, just doing the implementation without all the rest of the supporting infrastructure, so that at the sprint review you can call this done. Oooh, it's so tempting! C'mon, the product owner isn't going to look at your CruiseControl outputs....&lt;br /&gt;&lt;br /&gt;But now, what you've done is that you've incurred &lt;span style="font-style:italic;"&gt;technical debt&lt;/span&gt;. All that code doesn't have automated test coverage or adequate documentation, and probably has more complexity than it needs because you didn't get a chance to clean it up and refactor it. Now it's going to be harder for someone else to work on it (including you, two months down the line!), because it's inadequately documented and complex, and it's even hard to refactor it without introducing bugs because you don't have the automated tests to help you know if you covered everything! Thus, future work on this codebase gets bogged down a little bit by all this cruft.&lt;br /&gt;&lt;br /&gt;Now, technical debt works just like credit card debt: if you keep racking it up, it really starts to kill you. Because you are now working slower, you again find yourself behind the 8-ball of not being able to finish everything by the end of the sprint, and you rack up even more technical debt. Our product owner recently commented to me that he felt like we were getting less done than we used to at the beginning of the project with fewer people, and I'm starting to think it's all this accrued technical debt. &lt;br /&gt;&lt;br /&gt;Unfortunately, getting out of technical debt is pretty similar to credit card debt, and is also probably psychologically just as hard. The first step is cut up the credit cards -- refuse to accrue any more technical debt from this time forward, and instead only demo things at the review that are really fully "done", even if this means you don't get to everything you thought you would. This takes a certain degree of courage, and is probably something to do right after a major release, rather than just in front of one....&lt;br /&gt;&lt;br /&gt;Then, start paying down the debt. There are a number of ways of measuring this debt, many of them easily automated with &lt;a href="http://maven.apache.org/"&gt;Maven&lt;/a&gt;, such as:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://maven-plugins.sourceforge.net/maven-cobertura-plugin/"&gt;Cobertura&lt;/a&gt; - unit test code coverage measurement&lt;br /&gt;&lt;li&gt;&lt;a href="http://maven.apache.org/plugins/maven-pmd-plugin/"&gt;PMD&lt;/a&gt; - static analysis to identify unclean code patterns, including code complexity and design issues&lt;br /&gt;&lt;li&gt;Javadoc output - how many of your packages are missing documentation? how many classes don't have class documentation, and how many of the public/protected methods are missing javadocs? Do you have any javadoc warnings?&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;So the point is, you can measure all of these things, and come up with some numerical measure (pick some formula you like) of your technical debt. Furthermore, if you are tracking the user story velocities of your teams, you can actually start correlating your technical debt measurements at the beginning of a sprint with the story point velocity output by that sprint. In fact, you may even be able to figure out exactly how much drag your technical debt is putting on development, which means, based on your average cost for developer time, you can actually put a dollar amount on it. &lt;span style="font-style:italic;"&gt;You can actually tell how much interest you're paying on your technical debt in real dollars.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Then see how the conversation with your product owner goes when you ask for some technical debt paydown to get prioritized on the product backlog....&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-1301941150014002696?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/mdPlKwdHKW6Y_iQIWlRinTKJ2pQ/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/mdPlKwdHKW6Y_iQIWlRinTKJ2pQ/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/mdPlKwdHKW6Y_iQIWlRinTKJ2pQ/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/mdPlKwdHKW6Y_iQIWlRinTKJ2pQ/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/1301941150014002696/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=1301941150014002696" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/1301941150014002696?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/1301941150014002696?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/0FVRH8Ookq8/whats-apr-on-your-technical-debt.html" title="What's the APR on your technical debt?" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2007/12/whats-apr-on-your-technical-debt.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEIAR3c5fCp7ImA9WxZQGU0.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-8830213944449565163</id><published>2007-12-05T23:34:00.001-05:00</published><updated>2008-02-24T21:29:06.924-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-24T21:29:06.924-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="joel on software" /><category scheme="http://www.blogger.com/atom/ns#" term="evidence-based scheduling" /><category scheme="http://www.blogger.com/atom/ns#" term="estimation" /><category scheme="http://www.blogger.com/atom/ns#" term="scrum" /><category scheme="http://www.blogger.com/atom/ns#" term="monte carlo simulation" /><title>Scrum thoughts: Improving individual estimation</title><content type="html">In my &lt;a href="http://codeartisan.blogspot.com/2007/12/scrum-thoughts-fixed-feature-set-vs.html"&gt;last post&lt;/a&gt;, I described that our team routinely overestimates the amount of work it can finish in a sprint. The amount of this overestimation is quite high at times - sometimes we only finish two-thirds of what we initially plan.&lt;br /&gt;&lt;br /&gt;So how can we get our estimation better? I think there are two sources of estimation error: one is how many hours per day you actually have to apply to sprint tasks; the other is how many hours it will take you to actually finish something.&lt;br /&gt;&lt;br /&gt;In his article on &lt;a href="http://www.joelonsoftware.com/items/2007/10/26.html"&gt;Evidence-Based Scheduling&lt;/a&gt;, Joel Spolsky describes how we can compare the history of a developer's estimates vs. the actual time required to get an error factor in their estimates. I think this does a good job of addressing the second type of error, but you need to account for both if you are going to get an accurate estimate of how much you can do in a sprint.&lt;br /&gt;&lt;br /&gt;For example, as a team lead, I often get interrupted while coding when someone asks me a technical question, or to participate in a discussion on the dev mailing list, etc. I usually "leave the clock running" for these things, so I think evidence-based scheduling would cover this kind of inaccuracy in my estimation. However, I also get scheduled to attend extra meetings which don't go towards finishing sprint tasks, and I &lt;b&gt;don't&lt;/b&gt; leave the clock running for that. I also sometimes forget to log all my time spent against tickets, even though I update the remaining time on the tickets to make the burndown accurate. So that's another source of inaccuracy.&lt;br /&gt;&lt;br /&gt;Fortunately, I think there's a pretty simple solution that covers all these things. Let's assume we recorded in the past sprint (or past few sprints) how many working days someone was going to be available. Then we simply take the time logged against tickets &lt;b&gt;that were originally planned for in the sprint&lt;/b&gt; and divide that by the number of days worked to get a per-day burndown figure. If someone works nights and weekends, they'll just have a higher per-day burndown rate. If someone routinely has other duties that keep them off sprint tasks (e.g. doubling as a scrum master), their per-day burndown will be lower. If someone forgets to book time, their per-day burndown will be lower. If they routinely forget to plan for required elements of a task or if the product owner injected some new requirements mid-sprint, the per-day burndown goes down.&lt;br /&gt;&lt;br /&gt;Now, once you know that, you can use the evidence-based scheduling trick from Joel's article to use the actual task estimation history (here's where we compare original estimates vs. actual estimates for &lt;b&gt;finished&lt;/b&gt; tasks) to put an actual confidence level on how likely that developer will have a set of estimated tasks done by a certain time.&lt;br /&gt;&lt;br /&gt;So, during planning, you let someone estimate hours as if they would get to spend uninterrupted time on it, and then you can use the EBS plus per-day burndown to monitor whether something will likely get done by the end of the sprint.&lt;br /&gt;&lt;br /&gt;To make it concrete, let's say we want to put a 95% confidence level on being able to finish all sprint tasks. So, for each new ticket I write, we go through the following exercise:&lt;br /&gt;&lt;br /&gt;100 times, do:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;  &lt;li&gt;for each ticket so far, select a random estimate ratio from my estimation history, multiply by the original estimate to get a likely actual time&lt;br /&gt;  &lt;li&gt;add up total likely time, divide by per-day burndown figure to get a likely number of days&lt;br /&gt;  &lt;li&gt;if likely days &lt;= days available this sprint, trial is a success&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;If you have at least 95 successes, you may take on that task for that sprint. Otherwise, you're not allowed to commit to it.&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-8830213944449565163?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/Q6meLU_oPhI060gzLNO50cOUMtw/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Q6meLU_oPhI060gzLNO50cOUMtw/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/Q6meLU_oPhI060gzLNO50cOUMtw/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Q6meLU_oPhI060gzLNO50cOUMtw/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/8830213944449565163/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=8830213944449565163" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/8830213944449565163?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/8830213944449565163?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/YQV4pnpo1qs/scrum-thoughts-improving-individual.html" title="Scrum thoughts: Improving individual estimation" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2007/12/scrum-thoughts-improving-individual.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEIEQHo_cSp7ImA9WxZQGU0.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-7008532655196515198</id><published>2007-12-05T22:51:00.001-05:00</published><updated>2008-02-24T21:28:21.449-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-24T21:28:21.449-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="product owner" /><category scheme="http://www.blogger.com/atom/ns#" term="timeboxing" /><category scheme="http://www.blogger.com/atom/ns#" term="iteration length" /><category scheme="http://www.blogger.com/atom/ns#" term="release management" /><category scheme="http://www.blogger.com/atom/ns#" term="estimation" /><category scheme="http://www.blogger.com/atom/ns#" term="backlog" /><category scheme="http://www.blogger.com/atom/ns#" term="scrum" /><title>Scrum thoughts: Fixed feature set vs. fixed time</title><content type="html">I think we're going to enter into an interesting discussion with our product owners where they would like to propose moving away from a fixed-length (in time) iteration model to a fixed feature set model. The claim is that it is too hard to maintain a release roadmap when the set of features that comes out of a sprint is fluid.&lt;br /&gt;&lt;br /&gt;[Sidebar: our sprint teams routinely overestimate how much we can accomplish, so items are always falling off the end of our sprints. We've mitigated this somewhat by at least marking the bottom part of our sprint backlog as "low confidence items", but the truth is we rarely get to those items, &lt;em&gt;i.e.&lt;/em&gt;, we never totally finish our sprint backlog.]&lt;br /&gt;&lt;br /&gt;So I think there's a valid complaint here, however, I think it is misplaced. We also never actually get far enough ahead on story point estimation with the product team to have more than a sprint's worth of rough LOEs on things. So I'm not sure that any roadmap that would get produced would have any grounding in actual engineering estimates.&lt;br /&gt;&lt;br /&gt;So now that I think about it, this is probably more of a version numbering problem more than anything else. We increment our version numbers with every sprint, and furthermore release the output of every sprint to production. So I think the issue is that, for internal consumption's sake, our product owner can't say "the following features will be in 1.7, these other ones in 1.8, etc."&lt;br /&gt;&lt;br /&gt;It sounds like what we want to do is to choose a set of features for a release, and then simply run iterations until either that feature set is complete, or the product owner decides that the output of some iteration is good enough to release. Naturally, of course, now that I've written this out, this is the by-the-book definition of how to do release management with scrum. Not quite sure how we got away from doing things that way.&lt;br /&gt;&lt;br /&gt;I'm pretty convinced we should stick with the fixed-length iteration method, mainly for the reprioritization effect it has at planning. For every sprint where we had carryover, big chunks of that carryover were routinely deferred down the priority list from sprint to sprint, suggesting that we actually produced &lt;b&gt;more&lt;/b&gt; product value than we would have if we had just finished each sprint straight out.&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-7008532655196515198?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/7tZcwiy7SR_wI0wCGiGG36mI-Z8/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/7tZcwiy7SR_wI0wCGiGG36mI-Z8/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/7tZcwiy7SR_wI0wCGiGG36mI-Z8/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/7tZcwiy7SR_wI0wCGiGG36mI-Z8/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/7008532655196515198/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=7008532655196515198" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/7008532655196515198?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/7008532655196515198?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/vBhni0Qf9EU/scrum-thoughts-fixed-feature-set-vs.html" title="Scrum thoughts: Fixed feature set vs. fixed time" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2007/12/scrum-thoughts-fixed-feature-set-vs.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEMAR3oyfSp7ImA9WxZQGU0.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-8551976568283107475</id><published>2007-10-16T21:26:00.001-04:00</published><updated>2008-02-24T21:27:26.495-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-24T21:27:26.495-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="waterfall" /><category scheme="http://www.blogger.com/atom/ns#" term="sprint" /><category scheme="http://www.blogger.com/atom/ns#" term="prototype" /><category scheme="http://www.blogger.com/atom/ns#" term="scrum" /><title>Scrum thoughts: Staggering sprint teams</title><content type="html">When you have two or more sprint teams dedicated to a product, I think there's an opportunity to stagger their efforts in the following way. Let's track a user story through two sprints. In the first sprint, the &lt;em&gt;prototyping&lt;/em&gt; sprint, design, UX, and development cooperate to put together a working proposal for a user story. At the sprint review, if possible, the whole user story will be complete, where design, UX, and implementation are all consistent. &lt;br /&gt;&lt;br /&gt;This is very important, because one of weaknesses of the traditional waterfall method is that design, UX, and implementation must be consistent before the software is finished. Obviously, the implementation will be influenced by the design and wireframes, the wireframes will be influenced by what is visually possible on the page, the design will be influenced by the functionality required by the wireframes, the design will be influenced by what is hard or easy to realize in HTML/CSS, the wireframes and functionality will be influenced by what is feasible to build in the backend (whether from an LOE standpoint or from a performance point of view). The waterfall method doesn't allow for the full richness of this interaction to help arrive at the "optimal" way to solve a user story from &lt;b&gt;all&lt;/b&gt; points of view.&lt;br /&gt;&lt;br /&gt;However, if we are considering features where we want more lead time than a month, then it may not be possible to get all the way to a finished feature in one sprint. Instead, it may be enough to get to a consistent prototype, where there are designs, wireframes, and mostly-working code that can be shown at the review. All of the creative output may not be integrated yet, but it should be close enough to know that it can be gotten to a consistent state.&lt;br /&gt;&lt;br /&gt;This is where the next sprint comes into play: the &lt;em&gt;finishing&lt;/em&gt; sprint, where the mockups are brought to production by the &lt;em&gt;same team&lt;/em&gt;. Naturally, they will take product owner feedback from the review into consideration during the finishing sprint.&lt;br /&gt;&lt;br /&gt;In the simplest case, let's say we have two scrum teams. We can stagger their sprints such that when one team is in the prototyping sprint, the other is in the finishing sprint; this way we're still able to release new functionality to production after every sprint while still enabling cross-functional development of larger features.&lt;br /&gt;&lt;br /&gt;However, we can actually take things a step further: if, during pre-planning, we can roughly separate the stories into "small" (can be taken from start to finish in one sprint) and "large" stories, then we can actually mix things out in interesting ways, like:&lt;br /&gt;&lt;br /&gt;&lt;table&gt;&lt;br /&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Sprint 1&lt;/th&gt;&lt;th&gt;Sprint 2&lt;/th&gt;&lt;th&gt;Sprint 3&lt;/th&gt;&lt;th&gt;Sprint 4&lt;/th&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;Team 1&lt;/td&gt;&lt;td align="center"&gt;SF&lt;/td&gt;&lt;td align="center"&gt;SF&lt;/td&gt;&lt;td align="center"&gt;SF&lt;/td&gt;&lt;td align="center"&gt;SF&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;Team 2&lt;/td&gt;&lt;td align="center"&gt;LP&lt;/td&gt;&lt;td align="center"&gt;LF&lt;/td&gt;&lt;td align="center"&gt;LP&lt;/td&gt;&lt;td align="center"&gt;LF&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;Team 3&lt;/td&gt;&lt;td align="center"&gt;SF&lt;/td&gt;&lt;td align="center"&gt;LP&lt;/td&gt;&lt;td align="center"&gt;LF&lt;/td&gt;&lt;td align="center"&gt;FP&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;Team 4&lt;/td&gt;&lt;td align="center"&gt;SF/LP&lt;/td&gt;&lt;td align="center"&gt;SF/LF&lt;/td&gt;&lt;td align="center"&gt;SF/LP&lt;/td&gt;&lt;td align="center"&gt;SF/LF&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;Here:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;SF&lt;/b&gt; : finishing sprint for small user stories&lt;br /&gt;&lt;li&gt;&lt;b&gt;LP&lt;/b&gt; : prototyping sprint for large user stories&lt;br /&gt;&lt;li&gt;&lt;b&gt;LF&lt;/b&gt; : finishing sprint for large user stories&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;So here we have Team 1 kept full with small user stories every sprint. Teams 2 and 3 are kept full with large user stories, but staggered (note how Team 3 starts with a small finishing sprint so they have something to do during Sprint 1!). Then Team 4 operates under a model where some of their user stories are small, and some are large.&lt;br /&gt;&lt;br /&gt;I think the only operational trickiness here, from a software development standpoint, is that any "LP" work needs to happen in a branch and not the main development trunk, because it will not be production-ready by the end of the sprint. When the product owners finally sign off on an LP prototype as being ready (it's always possible that the product owners will not like--or will require significant enough changes to--a user story solution, thus requiring a follow-on LP sprint), then the first task of the LF sprint would be to port the branch back into the trunk and continue on.&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-8551976568283107475?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/WnDglXVQ6HDVvptue2Pz0zaG0gg/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/WnDglXVQ6HDVvptue2Pz0zaG0gg/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/WnDglXVQ6HDVvptue2Pz0zaG0gg/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/WnDglXVQ6HDVvptue2Pz0zaG0gg/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/8551976568283107475/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=8551976568283107475" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/8551976568283107475?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/8551976568283107475?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/J26bEr18ZNc/scrum-thoughts-staggering-sprint-teams.html" title="Scrum thoughts: Staggering sprint teams" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2007/10/scrum-thoughts-staggering-sprint-teams.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkUGQHY6eCp7ImA9WxRTGU4.&quot;"><id>tag:blogger.com,1999:blog-631933842798219175.post-8721176320527500342</id><published>2007-10-16T20:48:00.002-04:00</published><updated>2008-09-09T00:23:41.810-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-09-09T00:23:41.810-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="burndown" /><category scheme="http://www.blogger.com/atom/ns#" term="scrum of scrums" /><category scheme="http://www.blogger.com/atom/ns#" term="confidence" /><category scheme="http://www.blogger.com/atom/ns#" term="scrum" /><title>Scrum thoughts: scrums-of-scrums and daily scrums</title><content type="html">After planning, we dive right in to having daily scrums. We've gotten these to be pretty efficient (today we had a 10-person scrum finish in around 8 minutes). One key thing is starting on time--we charge people $1 if they are late to the scrum, and no one (not even the scrum master) is exempt. When we get enough saved up, we make a donation to charity (there was a thought that this should go to buy donuts for the team, but that felt a little like rewarding ourselves for being late). Our scrums are mainly a forum for people to schedule ad-hoc meetings with the people they are blocked on ("right after scrum in my office" being the most common meeting that gets scheduled). But running individual scrums is pretty well-documented and understood in the literature, I think.&lt;br /&gt;&lt;br /&gt;As I mentioned in &lt;a href="http://codeartisan.blogspot.com/2007/10/scrum-thoughts-sprint-planning.html"&gt;my post on sprint planning&lt;/a&gt;, we track individual burndowns on a daily basis -- how many hours does each person have left against each user story. At our scrum-of-scrums, which happens twice a week, the user story statuses for each team are put together to get an overall sprint status for the product. If you'll recall, we kept track of the global priorities of the user stories in our &lt;a href="http://codeartisan.blogspot.com/2007/10/scrum-thoughts-pre-planning.html"&gt;pre-planning session&lt;/a&gt;, so we can put this together in global priority order.&lt;br /&gt;&lt;br /&gt;Then based on the time remaining in the sprint, we can again reassess confidence levels for user stories:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;high confidence&lt;/b&gt; (green) : requires less than 50% of each person's remaining time&lt;br /&gt;&lt;li&gt;&lt;b&gt;medium confidence&lt;/b&gt; (yellow) : requires less than 80% of each person's remaining time&lt;br /&gt;&lt;li&gt;&lt;b&gt;low confidence&lt;/b&gt; (red) : requires more than 80% of someone's remaining time (possibly even getting into the "punt" range -- someone doesn't have enough time to finish their part this sprint, because they are overbooked)&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;The scrum-of-scrums is then a rebalancing effort. If one team falls behind, then you start to see a "striping" effect, where some of their user stories start falling in confidence, even though the surrounding user stories from the other teams stay the same. If you actually color these reports, it becomes pretty visually obvious. The rebalancing is all about trying to swap resources around such that the following principle holds:&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;&lt;em&gt;No user story should have a lower confidence level than a user story with lower priority.&lt;/em&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;Ways that we rebalance include swapping tasks from one team to another (this is easier when the teams are vertical striped across architectural levels, as its likely that another team will still be able to do the task in question; however, a similar effect can sometimes be achieved by moving functionality from one architectural level to another (e.g. computing something in the middleware vs. the database)), or by rescoping user stories to make them easier.&lt;br /&gt;&lt;br /&gt;Unfortunately, the way we are running sprints now ties the teams down a bit much to always make this flexibility possible. For example, a user story might say "put a Flash widget on the page that does X", and if we only have one Flash guy, we're pretty much stuck. If instead the user story said something more like: "we want a user to be able to see or do A,B,C on the page", then we might have some alternatives if the Flash guy gets overbooked.&lt;div class="blogger-post-footer"&gt;&lt;p&gt;
&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-1136626927500183";
/* 468x60, created 9/8/08 */
google_ad_slot = "4192662419";
google_ad_width = 468;
google_ad_height = 60;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;
&lt;/p&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/631933842798219175-8721176320527500342?l=codeartisan.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/LBSHjKclnhhJY_sT-ofLIQ-odbY/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/LBSHjKclnhhJY_sT-ofLIQ-odbY/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/LBSHjKclnhhJY_sT-ofLIQ-odbY/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/LBSHjKclnhhJY_sT-ofLIQ-odbY/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;</content><link rel="replies" type="application/atom+xml" href="http://codeartisan.blogspot.com/feeds/8721176320527500342/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=631933842798219175&amp;postID=8721176320527500342" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/8721176320527500342?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/631933842798219175/posts/default/8721176320527500342?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/codeartisan/~3/PuDFCDJlIoM/scrum-thoughts-scrums-and-scrums-of.html" title="Scrum thoughts: scrums-of-scrums and daily scrums" /><author><name>Jon Moore</name><uri>http://www.blogger.com/profile/16766484929210129406</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="17872496532947340338" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://codeartisan.blogspot.com/2007/10/scrum-thoughts-scrums-and-scrums-of.html</feedburner:origLink></entry></feed>
