<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-2341502915119631850</id><updated>2010-07-04T05:07:19.945+01:00</updated><title type='text'>The Puf Principle</title><subtitle type='html'>Frank van Puffelens thoughts and ideas on programming, software development and technology in general.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default?start-index=26&amp;max-results=25'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>68</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-8074213427280409113</id><published>2010-06-13T05:14:00.003+01:00</published><updated>2010-06-13T05:23:12.039+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='font design doodle'/><title type='text'>Block font</title><content type='html'>Many people seem to doodle while they're in meetings. Doodling is a nice way to distract yourself with something, while ensuring that you can still follow what is being presented. I guess that's why those meeting centers hand out those incredibly thin notebooks. &lt;br /&gt;&lt;br /&gt;My doodles typically consist of writing down words that have to do with what is being presented. Now before you say "that is called: taking notes" - I take my notes in elaborate fonts.&lt;br /&gt;&lt;br /&gt;One of the fonts I came up with this time is a 'block font'. And since the meeting was rather long, I decided to create all letters.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_QCmUgDEhTiw/TBRcd1H7s5I/AAAAAAAAEpM/r343eGbRJmw/s1600/blockfont.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 300px;" src="http://1.bp.blogspot.com/_QCmUgDEhTiw/TBRcd1H7s5I/AAAAAAAAEpM/r343eGbRJmw/s400/blockfont.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5482108313963049874" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I should apologize to the speakers, because I wasn't paying too much attention near the end of their presentation. Coming up with all letters in a single style is hard work.&lt;br /&gt;&lt;br /&gt;I'm quite happy with most of the letters, but there's some that I really can't get to look decent. The I and the J are especially painful. Is there somebody with real designer skills that can show me how to do those character properly in this style?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-8074213427280409113?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/8074213427280409113/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=8074213427280409113' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/8074213427280409113'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/8074213427280409113'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2010/06/block-font.html' title='Block font'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_QCmUgDEhTiw/TBRcd1H7s5I/AAAAAAAAEpM/r343eGbRJmw/s72-c/blockfont.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-3465205906559728909</id><published>2010-04-10T19:03:00.009+01:00</published><updated>2010-04-10T21:04:10.843+01:00</updated><title type='text'>What I use my iPad for</title><content type='html'>It's been a week since Apple released their latest revolution: the iPad. By now every major site has reviewed it (most positive), but unsure what the device is meant for. I got my iPad on the launch date, so have had a week to play around with it and show it off.&lt;br /&gt;&lt;br /&gt;In the past week the number one question from everyone has been: what do you use your iPad for? As with any new device type, the most common use-cases are not immediately clear. I don't think Apple ever thought the appstore would be the greatest attractor of new users to the iPhone. The iPad is no different: it's a completely new beast and it'll take some time for people to figure out what to do with it.&lt;br /&gt;&lt;br /&gt;In the past week, I've used my iPad primarily for these three things:&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Games&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Surfing&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Email&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;In addition I've actually also spent quite some time showing the iPad off to various people. But that is of course a use-case that will be less relevant over time. Until that happens though, I'd advice every iPad owner to buy the apps in the iWorks suite. They show off the serious side of the iPad best for the moment.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Games&lt;/h3&gt;&lt;br /&gt;Not surprisingly the iPad makes a great device for games. For me it has been mostly casual games so far, but I might try out some more serious games (red alert, need for speed) soon. &lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_QCmUgDEhTiw/S8DVhW49MqI/AAAAAAAAEgc/7EDEHh16CIY/s1600/213722-3%5B1%5D.jpg_rand%3D6DB56029-07B1-9AB2-76C12BBFFAF686F7"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 305px;" src="http://1.bp.blogspot.com/_QCmUgDEhTiw/S8DVhW49MqI/AAAAAAAAEgc/7EDEHh16CIY/s400/213722-3%5B1%5D.jpg_rand%3D6DB56029-07B1-9AB2-76C12BBFFAF686F7" border="0" alt=""id="BLOGGER_PHOTO_ID_5458597517429912226" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_QCmUgDEhTiw/S8DWiXJPz4I/AAAAAAAAEgk/J5XdqOAmC8U/s1600/HarborMasterHD-525x393%5B1%5D.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 299px;" src="http://3.bp.blogspot.com/_QCmUgDEhTiw/S8DWiXJPz4I/AAAAAAAAEgk/J5XdqOAmC8U/s400/HarborMasterHD-525x393%5B1%5D.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5458598634189737858" /&gt;&lt;/a&gt;&lt;br /&gt;My favorite games so far have been Harbor Master, Rush Hour and Labyrinth. All three seem to have been made exactly for this type of device, but with Rush Hour the playing pieces feel a bit too big. Maybe they're ahead of the pack and already made their game for the smaller iPad that is rumored to come next year.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Surfing&lt;/h3&gt;&lt;br /&gt;The iPad comes with Apple's latest Safari release, which is perfect for surfing the web. It doesn't allow tabbed browsing, which I think is a miss. But you can keep multiple pages open and switching between them is relatively elegant. When you shut down/restart Safari the pages take a bit of time to reload, which I think should not be needed on a device with 16GB or more of memory.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_QCmUgDEhTiw/S8DW_2THg-I/AAAAAAAAEgs/MNLzQfS-AGA/s1600/img002320x480%5B1%5D.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 267px; height: 400px;" src="http://1.bp.blogspot.com/_QCmUgDEhTiw/S8DW_2THg-I/AAAAAAAAEgs/MNLzQfS-AGA/s400/img002320x480%5B1%5D.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5458599140768842722" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The iPad will not soon replace your laptop as your main device for surfing. Although it is totally usable, it is just not made for using at your desk. Maybe that changes when I get a docking station later this month, but for the moment the laptop wins the battle for my desk.&lt;br /&gt;&lt;br /&gt;Where the iPad really shines is on the couch. I had some discussion with friend who'd say they use their laptop on the couch too. Maybe they do, but it's not the same thing. Whether I'm talking to visiting friends or are watching TV, the iPad is always within immediate reach. You can just leave it turned on all the time, it turns off the screen automatically and never gets hot nor does it ever make any noise. A laptop would simply not fit comfortably in the same seat as me or be waiting on the armrest of my chair just in case I need to look something up. Hmm... I think I've seen that actor before, let's look on IMDB quickly - no need to wait for a commercial break.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Email&lt;/h3&gt;&lt;br /&gt;My number three usage has been reading and responding to email. The on-screen keyboard is quite good, especially in landscape mode. Although as Apple claimed the keys are almost full-size, it does take some time to adapt to them. Without tactile feedback it is very easy to touch a few keys without noticing. So what I see most people do very quickly is arch their fingers a bit more to avoid those accidental key presses.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_QCmUgDEhTiw/S8DXSRpgqaI/AAAAAAAAEg0/3TzqPNcdizc/s1600/iPad-email%5B1%5D.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 278px;" src="http://4.bp.blogspot.com/_QCmUgDEhTiw/S8DXSRpgqaI/AAAAAAAAEg0/3TzqPNcdizc/s400/iPad-email%5B1%5D.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5458599457348168098" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Reading and writing email works like a charm. Switching between multiple mailboxes takes a bit of tapping, but nothing too bad. The iPad really came to my rescue this week when my laptop started acting up. Instead of having to fall back to my iPhone or having to steal somebody else laptop, I could switch to the iPad and write reasonably lengthy responses.&lt;br /&gt;&lt;br /&gt;So those are my top 3 use cases of the past week: gaming, surfing and mailing. There were also some things that I didn't use my iPad for, even though there is great software available and people had predicted those use-cases.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Not for reading&lt;/h3&gt;&lt;br /&gt;Apple tries to position the iPad as the ultimate eBook reader. I was already skeptical about this before I got my iPad, simply because I've grown to love my Sony Reader over the past half year. The one thing I love about the Sony is the eInk screen. &lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_QCmUgDEhTiw/S8DXiSzoTyI/AAAAAAAAEg8/emXkG3udwhI/s1600/ibooks%5B1%5D.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 266px;" src="http://4.bp.blogspot.com/_QCmUgDEhTiw/S8DXiSzoTyI/AAAAAAAAEg8/emXkG3udwhI/s400/ibooks%5B1%5D.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5458599732536954658" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Although the iPad's screen is gorgeous, for readability it doesn't compare to eInk. Try reading intensely for more than a few minutes and you'll notice the difference. The iPad might look fresher and cleaner, but reading an eInk screen is simply less stressful for the eyes than reading on the iPad. &lt;br /&gt;&lt;br /&gt;Another thing holding the iPad back as an eBook reader is it's size and weight. I often read my Sony in bed, lying on my back holding it above me. I challenge anyone to do that with the iPad for more than a few minutes. It's simply too heavy and too clunky.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_QCmUgDEhTiw/S8DYwoRFexI/AAAAAAAAEhE/gaFOICCr9yc/s1600/mzl_kdoumyiw_480x48_578424a%5B1%5D.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 300px;" src="http://2.bp.blogspot.com/_QCmUgDEhTiw/S8DYwoRFexI/AAAAAAAAEhE/gaFOICCr9yc/s400/mzl_kdoumyiw_480x48_578424a%5B1%5D.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5458601078327442194" /&gt;&lt;/a&gt;&lt;br /&gt;That said: the iBook software on the iPad looks great. Apple was kind enough to include an illustrated book of Winnie the Pooh, which perfectly shows the type of material the iPad was made for: colorful books with large fonts and pictures. I can easily see how the iPad might be a kids favorite reading device, even though I will stick to my Sony Reader for lengthy reading sessions.&lt;br /&gt;&lt;br /&gt;Where the iPad had proven useful is for reading articles. Whenever I want to read an article from a website, I've always had to convert it for reading on my Sony Reader. Although it's not a huge amount of work, I love using Instapaper for the iPad. With a single click on my laptop, I transfer any article from the web over to Instapaper on the iPad - where it is automatically reformatted into easy reading format. I really wish a similar program/feature existed for my Sony Reader though, because I do miss the eInk screen when reading on my iPad.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Not for video&lt;/h3&gt;&lt;br /&gt;I've also not been watching much video on my iPad. I think I should say "not yet" here, because there seems to be no reason not to use the iPad for watching youtube or video's bought from iTunes. &lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_QCmUgDEhTiw/S8DVMYs60NI/AAAAAAAAEgU/e33vsBGVU_8/s1600/YouTube-No-Flash-HTML-5-Video-Option-Great-for-Apple-Mac-OS-X%5B1%5D.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 328px;" src="http://2.bp.blogspot.com/_QCmUgDEhTiw/S8DVMYs60NI/AAAAAAAAEgU/e33vsBGVU_8/s400/YouTube-No-Flash-HTML-5-Video-Option-Great-for-Apple-Mac-OS-X%5B1%5D.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5458597157139042514" /&gt;&lt;/a&gt;&lt;br /&gt;Since the iPad doesn't support flash, many video sites may not work. But hopefully this is just a temporary nuisance until all sites do what youtube did: re-encode their video's to work in H.264 or whatever the format is.&lt;br /&gt;&lt;br /&gt;When I did watch video, it looked great on the screen. At time the wireless network at some places couldn't keep up with the video, which is an unfortunate consequence of the HD resolution that the iPad supports.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-3465205906559728909?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/3465205906559728909/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=3465205906559728909' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/3465205906559728909'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/3465205906559728909'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2010/04/what-i-use-my-ipad-for.html' title='What I use my iPad for'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_QCmUgDEhTiw/S8DVhW49MqI/AAAAAAAAEgc/7EDEHh16CIY/s72-c/213722-3%5B1%5D.jpg_rand%3D6DB56029-07B1-9AB2-76C12BBFFAF686F7' height='72' width='72'/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-101335961585575061</id><published>2010-03-04T19:49:00.000+01:00</published><updated>2010-03-04T19:51:04.228+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='planning poker'/><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='Scrum'/><title type='text'>Who estimates the stories in Scrum?</title><content type='html'>A few years ago we did a project with an outsourcing company. We gave them the current source code of a product, we gave them the backlog for the new version and we asked them to start implementing that backlog top to bottom.&lt;br /&gt;&lt;br /&gt;Since the external team was new to both the product and the Scrum process, I decided to also do some rough estimates myself. Given my experience with both the product and the process, those estimates could serve as a rough baseline.&lt;br /&gt;&lt;h3&gt;The progress&lt;/h3&gt;&lt;br /&gt;The remote team worked on the project for 5 sprints. During that time we saw an interesting trend.&lt;br /&gt;&lt;br /&gt;As the team finished each sprint, some of their work was accepted and other work wasn't. Work was sometimes rejected because of its quality, but often it was rejected because it simply didn't do everything that the product owner had expected. In those cases the team put another story on the backlog for the missing parts and provided a fresh estimate for the "new" work. They also re-estimated the existing stories based on the newly gained insight of the previous sprint.&lt;br /&gt;&lt;br /&gt;So over time the team's backlog grew in size. That is not uncommon, but look at the burnup chart:&lt;br /&gt;&lt;br /&gt;&lt;img src="http://apps.vanpuffelen.net/charts/burndown.jsp?days=1,2,3,4,5,6&amp;work=250,300,350,400,450,500&amp;work=0,13,30,54,97,117&amp;colors=1&amp;ideal=0" /&gt;&lt;br /&gt;&lt;em&gt;burnup 1: based on team estimates&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;Based on this burnup chart, the project is in serious trouble. The team seems to be doing great, delivering more work with every sprint. But even though more and more work is delivered every sprint, the team is not getting closer to the goal.&lt;br /&gt;&lt;br /&gt;The fifth sprint here is really disastrous: the team stopped increasing delivering more work. Did they burn out? How are we ever going to get the project out the door?&lt;br /&gt;&lt;br /&gt;For comparison let's look at the data based on my estimates - that the team never saw and didn't commit to:&lt;br /&gt;&lt;br /&gt;&lt;img src="http://apps.vanpuffelen.net/charts/burndown.jsp?days=1,2,3,4,5,6&amp;work=210,210,210,238,238,238&amp;work=0,10,18,25,39,43&amp;colors=1&amp;ideal=0" /&gt;&lt;br /&gt;&lt;em&gt;burnup 2: based on estimates by an external stakeholder&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;On my end, for every new story the team added to the backlog I checked if I had considered that part in my original estimate. If so, I split the story points I had originally estimated over the two stories. If the new story was functionality I had not expected in my estimate, I would provide a fresh estimate - also increasing the backlog in my part. As you can see that happened only once, but the increase was substantial (about 10%).&lt;br /&gt;&lt;br /&gt;In my chart too the team seems to be burning out a bit in sprint 5. But it doesn't seem half as bad as in the firsts burnup chart. They are still getting closer to the goal.&lt;br /&gt;&lt;br /&gt;And in both charts the team seems to be about 20% done with the total amount of work.&lt;br /&gt;&lt;h2&gt;Analysis&lt;/h2&gt;&lt;br /&gt;So what's the difference between the two charts and estimators. From my perspective there are two major differences: the stability of the progress and who made the estimates.&lt;br /&gt;&lt;h3&gt;How stable is the progress&lt;/h3&gt;&lt;br /&gt;The charts below show the velocity per sprint as derived from the burndown charts above:&lt;br /&gt;&lt;br /&gt;&lt;img src="http://apps.vanpuffelen.net/charts/burndown.jsp?days=1,2,3,4,5,6&amp;work=0,13,17,24,43,20&amp;colors=1&amp;ideal=0&amp;width=320&amp;height=240&amp;border=20"/&gt;&lt;br /&gt;&lt;em&gt;velocity 1: velocity from sprint to sprint in burndown 1&lt;/em&gt;&lt;br /&gt;&lt;img src="http://apps.vanpuffelen.net/charts/burndown.jsp?days=1,2,3,4,5,6&amp;work=0,10,8,7,14,4&amp;colors=1&amp;ideal=0&amp;width=320&amp;height=240&amp;border=20" /&gt;&lt;br /&gt;&lt;em&gt;velocity 2: velocity from sprint to sprint in burndown 2&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;The burnups don't really have a lot of data. But if you look at the first velocity chart, you can see that sprints 1 to 4 show a somewhat exponential growth in velocity (1.3x, 1.4x, 1.8x).&lt;br /&gt;&lt;br /&gt;The scope/goal line in the corresponding burnup chart (burnup 1) shows a constant growth, mostly because I don't have the exact data anymore. &lt;br /&gt;&lt;br /&gt;So at some point the two lines in burnup 1 are going to intersect, but it is pretty difficult to determine where they'll intersect with the naked eye.&lt;br /&gt;&lt;br /&gt;The second burnup doesn't have this growth in velocity and the scope increase is about 10% over 5 sprints.&lt;br /&gt;&lt;br /&gt;It is a lot easier to see where this project is going to end. And isn't scrum all about transparency and easy to use charts and data?&lt;br /&gt;&lt;h3&gt;Who provides the estimates?&lt;/h3&gt;&lt;br /&gt;The second question I posed above is who provides these estimates. With the whole hype about lean development I learned that one way to optimize output is to eliminate waste. Anything that isn't delivered to the customer is waste and should be eliminated.&lt;br /&gt;&lt;br /&gt;Who needs these estimates? The customer? I don't think my customer cares about estimates. He cares about getting as much software as possible for his/her money. In fact, while the team was providing these estimates they could also have been building more software.&lt;br /&gt;&lt;br /&gt;So is it the team that needs these estimates then? After all without those estimates, how do they know what they can commit to? Well... the team does need to know how big a story is before they can commit to it. But they only need to know that at the start of each sprint. And only for the stories that they think they might do in that sprint. So although the team needs to know the size of each story it commits to, it doesn't need to know the size of all stories at the start of the project. Nor do they need to re-estimate the stories (another form of waste).&lt;br /&gt;&lt;br /&gt;So the only person that actually needs those estimates, is the guy drawing the charts. In this case that was me, an external stakeholder who is not a part of the team. In many cases it will be the scrum master, who needs those estimates to give his stakeholders some view of the progress towards the overall goal. In other cases it will be the product owner, since he is most interested in seeing his return on investment.&lt;br /&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;br /&gt;My suggestion: &lt;b&gt;let the guy who needs them come up with the numbers&lt;/b&gt;. And if you don't feel comfortable, &lt;b&gt;do a reasonable guess&lt;/b&gt;. And if you don't even feel comfortable guessing, &lt;b&gt;set all stories to the same size&lt;/b&gt;. In the end it doesn't really matter too much and you'll allow the team to focus on what really matters: building working software.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-101335961585575061?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/101335961585575061/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=101335961585575061' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/101335961585575061'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/101335961585575061'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2010/03/who-estimates-stories-in-scrum.html' title='Who estimates the stories in Scrum?'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-5942864620759987088</id><published>2009-08-25T08:04:00.005+01:00</published><updated>2009-09-29T00:04:09.515+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='particle system'/><title type='text'>Java Particle Systems part 2: adding some life</title><content type='html'>The frame below shows a demonstration of the particle system we are about to build. Move your mouse through it to have some fun. If it doesn't work for you, click &lt;a href='http://apps.vanpuffelen.net/jsparticles/particles2.html' target='_blank'&gt;this link&lt;/a&gt; to open the same demo in a popup window.&lt;br /&gt;&lt;iframe src='http://apps.vanpuffelen.net/jsparticles/particles2.html' width='640' height='480' scrolling='no'&gt; &lt;/iframe&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Introduction&lt;/h3&gt;&lt;br /&gt;A few months ago I wrote &lt;a href="http://frank.vanpuffelen.net/2009/05/particle-systems-in-javascript.html"&gt;an article that demonstrated how to create a very simple particle system in less than 50 lines of JavaScript code&lt;/a&gt;. That article has slowly been finding its way into the Google search indexes and by now it's becoming obvious that I'm not the only one who thinks particle systems deserve more attention and JavaScript is the perfect language to experiment with them.&lt;br /&gt;&lt;br /&gt;Back then I was quite pleased with being able to implement a fully functional particle system in less than 50 lines of (quite readable) JavaScript code. But I think we can agree that the particle system was not very exciting. Although the system contained all three required ingredients, the end result was a bit "static". From games and other places where particle systems are used, we are used to them being a lot more "alive".&lt;br /&gt;&lt;br /&gt;So that's the goal for this article: to add some life to our previous particle system, while still keeping it small. In this case I've increased my self-imposed limit to a 100 lines. A generous doubling from our previous system, so we'd better have some real interesting "life" to show for the increase.&lt;br /&gt;&lt;br /&gt;We'll add three behaviors to the particle sytem.&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Gravity&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Grow then shrink&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Mouse control&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;Lot's of work to do, so let's get started with gravity straight away.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Gravity&lt;/h3&gt;&lt;br /&gt;In our first particle system, the particles would fall at a constant speed. The fall speed of each particle was determined at random when creating the particle.&lt;br /&gt;&lt;pre&gt;      dy: Math.random() + 0.5&lt;/pre&gt; &lt;br /&gt;And then we just updated the y position every time:&lt;br /&gt;&lt;pre&gt;      particles[i].y = particles[i].y + particles[i].dy;&lt;/pre&gt;&lt;br /&gt;I always find it very interesting to add some gravity-like behavior to systems I create. So how about making the particles fall at an increasing speed?&lt;br /&gt;&lt;pre&gt;      p = particles[i];&lt;br /&gt;      p.y += p.dy;&lt;br /&gt;      p.dy += 0.1; // simulate gravity&lt;/pre&gt;&lt;br /&gt;So we increase the particle's y velocity every time we update its position. I realize that to make this work like real gravity I'd have to know the particle's mass and do a multiplication. But for this simple example just increasing the dy is already convincing enough.&lt;br /&gt;&lt;br /&gt;Since the particle will now drop at an ever increasing speed, it is nice to make it go up a bit at first:&lt;br /&gt;&lt;pre&gt;      dy: -1 + Math.random()&lt;/pre&gt;&lt;br /&gt;So the particles will first move up a bit and then start falling down at an increasing speed. If all goes well, they'll move in a nice arc-like pattern. Again there is no real math or physics reason for these values, these just looked best to me. If you feel the need to make the system more physically correct, just download the source and go ahead. Let me know if you find an interesting combination.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Grow then shrink&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Another thing I wasn't too happy with in the original particle system is the static size that the particles have. Each particle is created to be 3px in width and height. By the way: there was a bug in the original code such that the sizing didn't work in Internet Explorer. As my team often tells me: "you need to test more with IE Frank!" My bad. Let's hope I got it right this time.&lt;br /&gt;&lt;br /&gt;I changed the code to shrink the particle a bit in each update:&lt;br /&gt;&lt;pre&gt;      p.s -= 0.1&lt;/pre&gt;&lt;br /&gt;This of course requires the particle to have a size when we create it:&lt;br /&gt;&lt;pre&gt;      p.s: 3.0&lt;/pre&gt;&lt;br /&gt;With this setup the particle will slowly shrink from 3 pixels width and height to 0. Once the size of the particle reaches 0, we can kill it:&lt;br /&gt;&lt;pre&gt;      if (p.ttl-- &gt; 0 &amp;&amp; p.s &gt; 0) {&lt;br /&gt;         updateParticleElement(p);&lt;/pre&gt;&lt;br /&gt;This leads to a working particle system, but somehow it's still a bit boring when all the particles start and shrink at the same speed. So let's make the size change random and instead of just shrinking, let's make the particles grow at first and then shrink until they die out.&lt;br /&gt;&lt;br /&gt;For the initialization the change is simple:&lt;br /&gt;&lt;pre&gt;      p.ds: Math.random()+0.1,&lt;/pre&gt;&lt;br /&gt;This gives the particle a growth speed that is guaranteed to be positive. So instead of adding 0.1 each time we update the particle, we add the ds:&lt;br /&gt;&lt;pre&gt;      p.s += p.ds;&lt;/pre&gt;&lt;br /&gt;If we leave this unchanged, the particle will grow forever - or at least until it's time to live runs out. To ensure the particle also starts shrinking at some point we give it a maximum size:&lt;br /&gt;&lt;pre&gt;      if (p.s &gt; 5) p.ds *= -1;&lt;/pre&gt;&lt;br /&gt;When the particle reaches its maximum size, we just flip the sign of the growth speed - so it becomes a shrink speed. After this the particle will keep shrinking until it reaches size zero at which point it dies out.&lt;br /&gt;&lt;br /&gt;By now I'm not really sure if the ttl property is still needed, as the particles already have an implicit time to live and get their randomness from the growth/shrink speed. But I left the ttl in as a safeguard against my own mistakes when manipulating the size. I wouldn't want to end up with an infinitely living and growing particle by mistake.&lt;br /&gt;&lt;br /&gt;If you would reconstruct the particle system at this point it is already quite lively and dynamic. Particles seem to be attracted by the bottom of your screen and particles seem to have a real life cycle, where they grow and shrink to die out. But this particle system is still a spectator sport. Let's see what happens when we give the user some control over what happens on screen.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Mouse control&lt;/h3&gt;&lt;br /&gt;As our last feature we want to give the user some control over the behavior of the particles. Specifically I want to accomplish two things:&lt;ol&gt;&lt;li&gt;particles originate close to the position of the mouse cursor&lt;/li&gt;&lt;br /&gt;&lt;li&gt;particles move roughly in the direction that the mouse is moving&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;To accomplish this we need to add four variables to the system. &lt;br /&gt;&lt;pre&gt;      mx and my - the position of the mouse&lt;br /&gt;      dmx and dmy - the direction and speed of the mouse's movement&lt;/pre&gt;&lt;br /&gt;These variables are global to the script, so they are not stored for each particle.&lt;br /&gt;&lt;br /&gt;These variables are updated whenever the user moves the mouse, by hooking the onmousemove event of the window. The mousemove handler is a bit tricky to read at first. But since the behavior of the particles only depends on the four variables mentioned here, I will not explain the mousemove handler of the code further.&lt;br /&gt;&lt;br /&gt;Now how do we use these variables when creating and updating a particle? The variables are actually only used when we create a new particle:&lt;br /&gt;&lt;pre&gt;      x: mx,&lt;br /&gt;      y: my,&lt;br /&gt;      dx: dmx + Math.random(),&lt;br /&gt;      dy: dmy + Math.random() - 1,&lt;/pre&gt;&lt;br /&gt;As you can see the code remain quite similar to how it was. We've just substituted our new "mouse dependent" variables for the previous hard coded and random values. We still make the dx and dy a bit random, since otherwise all particles would go in the same direction.&lt;br /&gt;&lt;br /&gt;The updating of the particles doesn't have to change for this behavior. The mouse just allows the user to control the position and the direction of the initial movement. After its creation each particle behaves as the rules of our system dictate, which ensures the particles still behave similar even with al their differing properties.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Summary&lt;/h3&gt;&lt;br /&gt;So in &lt;a href="http://frank.vanpuffelen.net/2009/05/particle-systems-in-javascript.html"&gt;the first article&lt;/a&gt; we created a simple particle system in less then 50 lines of JavaScript code. The system was fully functional, but felt a bit static after you'd been looking at it for a while.&lt;br /&gt;&lt;br /&gt;In this second article we've added life to our particle system by adding by:&lt;ol&gt;&lt;li&gt;making the environment a bit more "physical" with our simple gravity emulation&lt;/li&gt;&lt;br /&gt;&lt;li&gt;making the life cycle of the particles more complex, by making them grow and then shrink and die out&lt;/li&gt;&lt;br /&gt;&lt;li&gt;allowing the user to control the particles, by hooking their position and movement up to the mouse movement&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;All in all our particle system has now gotten quite complex. But weighing in at 90 lines, it is still well under my self-imposed limit of a 100 lines of code. I did my best to keep the code readable again and even added some constants to make it easier to change and debug the particle system. &lt;br /&gt;&lt;br /&gt;Now go ahead and play with the system. I find it's great fun to see how far up you can throw the particles. Remember: this is controlled by moving the mouse, so start pushing and pulling your mice!&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;The code&lt;/h3&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;// constants&lt;br /&gt;var NUM_PARTICLES = 50;&lt;br /&gt;var FPS = 20;&lt;br /&gt;var PARTICLE_LIFETIME = 4; // in seconds&lt;br /&gt;var ANIMATION_LIFETIME = 300; // in seconds&lt;br /&gt;&lt;br /&gt;// global variables&lt;br /&gt;var container;&lt;br /&gt;var particles = [];&lt;br /&gt;var framecount = 0;&lt;br /&gt;var mx = 20; // default origin, will be set to mouse position once the mouse moves&lt;br /&gt;var my = 20;&lt;br /&gt;var dmx = 1.0; // default movement, will be set to mouse movement once the mouse moves&lt;br /&gt;var dmy = -1.0&lt;br /&gt;&lt;br /&gt;function onMouseMove(e) {&lt;br /&gt; var IE = document.all?true:false&lt;br /&gt; var nmx, nmy;&lt;br /&gt; if (IE) { // grab the x-y pos.s if browser is IE&lt;br /&gt;  nmx = event.clientX + document.body.scrollLeft&lt;br /&gt;  nmy = event.clientY + document.body.scrollTop&lt;br /&gt; } else {  // grab the x-y pos.s if browser is NS&lt;br /&gt;  nmx = e.pageX&lt;br /&gt;  nmy = e.pageY&lt;br /&gt; }&lt;br /&gt; dmx = (nmx - mx) / 4;&lt;br /&gt; dmy = (nmy - my) / 4;&lt;br /&gt; mx = nmx;&lt;br /&gt; my = nmy;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function createParticle(container) {&lt;br /&gt; var p = {&lt;br /&gt;  elm: createParticleElement(),&lt;br /&gt;  x: mx,&lt;br /&gt;  y: my,&lt;br /&gt;  s: 0.0, // size&lt;br /&gt;  dx: dmx + Math.random(),&lt;br /&gt;  dy: dmy + Math.random() - 1,&lt;br /&gt;  ds: Math.random()+0.1,&lt;br /&gt;  ttl: PARTICLE_LIFETIME*FPS&lt;br /&gt; };&lt;br /&gt; container.appendChild(p.elm);&lt;br /&gt; updateParticleElement(p);&lt;br /&gt; return p;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function createParticleElement() {&lt;br /&gt; var elm = document.createElement('span');;&lt;br /&gt; elm.style.border = '1px solid blue';&lt;br /&gt; elm.style.position = 'absolute';&lt;br /&gt; return elm;&lt;br /&gt;}&lt;br /&gt;function updateParticleElement(p) {&lt;br /&gt; p.elm.style.width = p.elm.style.height = p.elm.minWidth = p.elm.minHeight = Math.floor(p.s) + 'px'; // doesn't work on IE&lt;br /&gt; p.elm.style.left = Math.floor(p.x) + 'px';&lt;br /&gt; p.elm.style.top = Math.floor(p.y) + 'px';&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function updateParticles() {&lt;br /&gt; for (var i=particles.length-1; i &gt;= 0; i--) {&lt;br /&gt;  var p = particles[i];&lt;br /&gt;  p.s += p.ds;&lt;br /&gt;  if (p.s &gt; 5) p.ds *= -1;&lt;br /&gt;  p.x += p.dx;&lt;br /&gt;  p.y += p.dy;&lt;br /&gt;  p.dy += 0.1; // simulate gravity&lt;br /&gt;  if (p.ttl-- &gt; 0 &amp;&amp; p.s &gt; 0) {&lt;br /&gt;   updateParticleElement(p);&lt;br /&gt;  } else {&lt;br /&gt;   container.removeChild(p.elm);&lt;br /&gt;   particles[i] = createParticle(container);&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; if (framecount++ &amp;lt; ANIMATION_LIFETIME*FPS) {&lt;br /&gt;  setTimeout('updateParticles()', Math.floor(1000 / FPS));&lt;br /&gt; } else {&lt;br /&gt;  alert("All done. Reload to watch again.");&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;window.onload = function() {&lt;br /&gt; container = document.getElementById('particles');&lt;br /&gt; for (var i=0; i &amp;lt; NUM_PARTICLES; i++) {&lt;br /&gt;  particles[i] = createParticle(container);&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; setTimeout('updateParticles()', Math.floor(1000 / FPS));&lt;br /&gt; document.onmousemove = onMouseMove;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;Download it &lt;a href="http://apps.vanpuffelen.net/jsparticles/particles2.js"&gt;here&lt;/a&gt;.&lt;br /&gt;And the corresponding HTML:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;html&gt;&lt;br /&gt;    &amp;lt;head&gt;&lt;br /&gt;        &amp;lt;title&gt;JavaScript Particle System&amp;lt;/title&gt;&lt;br /&gt;        &amp;lt;script type="text/javascript" src="particles2.js"&gt; &amp;lt;/script&gt;&lt;br /&gt;    &amp;lt;/head&gt;&lt;br /&gt;    &amp;lt;body&gt;&lt;br /&gt;        &amp;lt;div id="particles" style="width:640px; height: 480px; overflow: hidden"&gt; &amp;lt;/div&gt;&lt;br /&gt;    &amp;lt;/body&gt;&lt;br /&gt;&amp;lt;/html&gt;&lt;br /&gt;&lt;/pre&gt;Download it &lt;a href="http://apps.vanpuffelen.net/jsparticles/particles2.html"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;See also&lt;/h3&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://frank.vanpuffelen.net/2009/05/particle-systems-in-javascript.html"&gt;the first article, where we create a simple particle system in 50 lines of code&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://frank.vanpuffelen.net/2007/06/incremental-search-using-javascript.html"&gt;a demonstration of how to do incremental search in JavaScript&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://frank.vanpuffelen.net/2007/12/grid-computing-using-web-browsers.html"&gt;my ponderings on a computer grid consisting of browser running JavaScript&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://frank.vanpuffelen.net/2008/03/finding-primes-using-grid-of-browsers.html"&gt;how such a grid could be used to find primes&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-5942864620759987088?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/5942864620759987088/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=5942864620759987088' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/5942864620759987088'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/5942864620759987088'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2009/08/java-particle-systems-part-2-adding.html' title='Java Particle Systems part 2: adding some life'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-8255471722437938070</id><published>2009-07-25T20:50:00.001+01:00</published><updated>2009-07-25T20:58:51.440+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><title type='text'>Seven rules to keep your twitter followers</title><content type='html'>With Jon's recent &lt;a href="http://jonontech.com/2009/07/21/follow-forty-twitter-cms-gurus-in-three-clicks/"&gt;"top 40 list of CMS gurus"&lt;/a&gt; I suddenly got a lot of additional followers. And I added quite a few of those people to my twit-list too. From the first few days it looks like I might start unfollowing some soon, but there's definitely a few that are a great addition to the list. While scrolling through recent tweets, I sought what makes tweets interesting for me and things that I really don't like. So here is the list (in random order) of do's and don'ts to keep my as your follower on twitter.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;1. Yet another link retweeter&lt;/h3&gt;&lt;em&gt;"RT @cmswire Open Source for America: Change We Can Believe In? http://bit.ly/r9sj6 #opensource #oss"&lt;br /&gt;"RT @cmswire 39 More Ways to Get Your CMS Twitter Fix http://bit.ly/YiV9I"&lt;br /&gt;"RT @OpenText #OpenText Completes #Vignette Acquisition http://cli.gs/bUvzsN"&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;There's nothing wrong with retweeting something interesting you've heard somebody say. But if all you tweet is other people's tweets, I'd much rather listen to those other people.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;2. Your opinion matters to me&lt;/h3&gt;Personal tweeting is fine. I love to hear what friends and family are doing. But from people that don't fall into those categories, I don't need:&lt;br /&gt;&lt;br /&gt;&lt;em&gt;"Ottawa Organic Market on Bank.... "&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;Don't just tell me that you're at an organic market in whatever city. If we're not emailing or calling each other at least once a week, chances are I don't care that much about your whereabouts. Sounds harsh? It's not meant to be harsh.&lt;br /&gt;&lt;br /&gt;Because I apparently care enough about your opinion to follow your tweets. So how about you tell me your opinion on that market. &lt;br /&gt;&lt;br /&gt;&lt;em&gt;"the Ottawa organic market is really the best"&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;3. Challenge me&lt;/h3&gt;Even better, don't just tell me your opinion. Tease me to give you mine. It's OK to do a bit of trolling as long as long as you don't mind getting trolls back too.&lt;br /&gt;&lt;br /&gt;&lt;em&gt;"the Ottawa organic market is really the best, certainly beats the one in Amsterdam"&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;Now that's a great way start to a discussion. Because I really, really LOVE the organic market in Amsterdam.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;4. Don't forget I'm eavesdropping&lt;/h3&gt;One of the worst things you can do to me on twitter is to start a 1-on-1 conversation with somebody where you forgot that I'm still there reading along. &lt;br /&gt;&lt;br /&gt;&lt;em&gt;"Sorry? Barbecue where? Are you kidding me?"&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;What barbecue are you talking about? Where is it? Am I invited too? If you don't want to let me know these things, why are you sending your private message as a public tweet?&lt;br /&gt;&lt;br /&gt;Remember: if you want to have a private conversation, there is always still email or Google talk. Or even better &lt;code&gt;dm&lt;/code&gt; in twitter. &lt;br /&gt;&lt;br /&gt;But if you feel like you want to share your conversation with the world, give me enough context.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;5. Give me context&lt;/h3&gt;If talking to someone in a public tweet, make it clear who you're talking to.&lt;br /&gt;&lt;br /&gt;&lt;em&gt;"@john123 glad you liked the post!"&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;It certainly beats the tweet about &lt;em&gt;that&lt;/em&gt; barbecue. Because here I can at least check the other part of the conversation in john123's tweet stream. But still, giving me a bit of context goes a long way.&lt;br /&gt;&lt;br /&gt;&lt;em&gt;"@john123 glad you liked the post! http://is.gd/1JyHY"&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;One click and I'm at the post. But then again, why not just retweet the original and add your comment to it:&lt;br /&gt;&lt;br /&gt;&lt;em&gt;"glad you liked the post! RT @john123 good analogy on how to mix persuasive content with relational data in modern CMS. http://is.gd/1JyHY"&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;Phew... 138 characters, that's cutting it close. But it has as much context as you can add to your tweet.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;6. Tell me something I can use&lt;/h3&gt;A friend was apparently enjoying a milkshake a bit too much and had to share it with the world:&lt;br /&gt;&lt;br /&gt;&lt;em&gt;"Just had the best strawberry milkshake ever"&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;I like reading it, because I always love to hear about good food - especially from someone whose taste buds I trust.&lt;br /&gt;&lt;br /&gt;But how am I going to be able to do something with that? Tell me &lt;em&gt;where&lt;/em&gt; you had that milkshake. Believe me, I'd love to know - even if it is a three hour drive.&lt;br /&gt;&lt;br /&gt;&lt;em&gt;"had the best strawberry milkshake ever at Mill Creek near Mineral, NoCal"&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;Honey start the car, we're going for a drive!&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;7. I don't need more spam^H^H^H^H marketing&lt;/h3&gt;Corporate tweeting is hot these days. There are many corporate twitter accounts that are easily recognizable, because they consist of the company's name. But there are also plenty of innocent-looking names that generate a remarkable number of tweets that simply reiterate how great product xyz is or how well product abc would solve the problem I'm (not) having).&lt;br /&gt;&lt;br /&gt;I don't have a problem with these accounts. We're all luckily free to tweet whatever we want. But if you'd like to keep your followers: keep your marketing to content ratio below 20%.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;8. I read your blog already&lt;/h3&gt;Some people see link tweets as the natural progression of feed readers. I don't. I still have a healthy 100 or something feeds that I follow, just because they regularly show content that I like to read. &lt;br /&gt;&lt;br /&gt;In other words: if you have a blog that is of interest to me, chances are it's already in my Google reader. So if all that you are tweeting is links to your blog posts, following you is not really adding any value for me. Now where's that unfollow link?&lt;br /&gt;&lt;br /&gt;Mind you: there's nothing wrong with tweeting about your own posts. In fact that's what I'll do once I click the publish button for this post too. But if you want to keep me as a follower, be sure to add some original content into your tweets too.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;And yours?&lt;/h3&gt;&lt;br /&gt;Those are the rules that sprang to my mind as I was scrolling through the rapidly growing stream of tweets. Do you have any rules of your own? How can I make sure that you don't click the "unfollow" link on me?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-8255471722437938070?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/8255471722437938070/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=8255471722437938070' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/8255471722437938070'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/8255471722437938070'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2009/07/seven-rules-to-keep-your-twitter.html' title='Seven rules to keep your twitter followers'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-7019531822349426173</id><published>2009-07-06T20:38:00.002+01:00</published><updated>2009-07-06T20:41:15.774+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='IIS'/><category scheme='http://www.blogger.com/atom/ns#' term='NTLM'/><category scheme='http://www.blogger.com/atom/ns#' term='Safari'/><category scheme='http://www.blogger.com/atom/ns#' term='iframe'/><title type='text'>Safari 4 on Windows crashes when you provide incorrect credentials</title><content type='html'>Sorry for the long title of this post. But it is the most accurate description of a problem we're having in a project at the moment. Safari 4 on Windows seems to crash in what I perceive to be a common use case. Here are the minimal steps that I came up with for reproducing the problem:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;create a new web site in IIS&lt;/li&gt;&lt;br /&gt;&lt;li&gt;enable integrated authentication on this web site&lt;/li&gt;&lt;br /&gt;&lt;li&gt;connect to the new web site with Safari 4 for Windows&lt;/li&gt;&lt;br /&gt;&lt;li&gt;provide incorrect credentials&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;At this point Safari 4 crashes on my system.&lt;br /&gt;&lt;br /&gt;Providing correct credentials allows us to continue. But in the application my team is building, we will often encounter another authentication problems later on. &lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Safari pops up a log on box when we open a page from the same web site in an iframe. No matter which credentials we provide, we can't continue.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;I've been searching whether this is a known problem for a while now. But I haven't found a single page that points out the problem we encountered here. Hence this simple write-up.&lt;br /&gt;&lt;br /&gt;Has anyone had similar problems with Safari on Windows? If so, is there any way to work around the problem? Because as it is, we can't really support our application on Safari 4 on Windows. :-/&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-7019531822349426173?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/7019531822349426173/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=7019531822349426173' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/7019531822349426173'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/7019531822349426173'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2009/07/safari-4-on-windows-crashes-when-you.html' title='Safari 4 on Windows crashes when you provide incorrect credentials'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-5914405052816693922</id><published>2009-05-16T18:56:00.001+01:00</published><updated>2009-05-16T19:41:33.214+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='particle system'/><category scheme='http://www.blogger.com/atom/ns#' term='game'/><title type='text'>Particle systems in JavaScript</title><content type='html'>Particle systems have always held some magic for me. Or maybe I should explain it more clearly: anything that resembles physics in a game-like fashion has always held some magic to me. In this post we'll look at how to create a simple particle system in JavaScript.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Pod racers&lt;/h2&gt;&lt;br /&gt;A few years ago when I wrote a small 3D pod racer for my own amusement. If you don't know what pod racers are, have a look at this video:&lt;br /&gt;&lt;br /&gt;&lt;object width="425" height="344"&gt;&lt;param name="movie" value="http://www.youtube.com/v/TVQ1Vev33s8&amp;hl=en&amp;fs=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/TVQ1Vev33s8&amp;hl=en&amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;My game looked nothing like that of course. I'm a developer, not an artist. And besides I spent lots of time playing with the physics of the pod: where are the thrusters on my pod? How much force do the thrusters give? Does the force get exponentially bigger when the pod gets closer to the track? Fun stuff.&lt;br /&gt;&lt;br /&gt;I also added a particle system to my pod. After all, those thrusters are bound to give off some exhaust fumes and this was a great way to give an indication of speed. And of course it was a great way for me to get started on particle systems.&lt;br /&gt;&lt;br /&gt;As it turns out particle systems are really quite simple things. I had a blast playing with the parameters and figuring out how to create a most interesting effects.&lt;br /&gt;&lt;br /&gt;Of course the pod racer was never finished and the source code has long been lost. That's why today we'll create a completely new particle system. In less than 100 lines of JavaScript. Granted it will only be 2D this time. But believe me, I've already been staring at the flow of particles longer now than it took me to write the script.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;parts of a particle system&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Let's get started with the three things that make up a particle system:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;an array of particle data structures&lt;/li&gt;&lt;br /&gt;&lt;li&gt;a function that updates all particles each frame&lt;/li&gt;&lt;br /&gt;&lt;li&gt;some form of lifetime management for the particles&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h2&gt;Parts of a particle&lt;/h2&gt;&lt;br /&gt;First comes the particle data structure. What makes up a particle? If you've ever seen a particle system in its bare essentials, you'll have noticed that it consists of multiple small elements on your screen that each move on their own account, yet in a predictable and similar way.&lt;br /&gt;&lt;br /&gt;This already means quite a few things. A particle apparently has a location on the screen. And since it moves, it must also have a direction.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;var particle = {&lt;br /&gt;  x: 0.0,&lt;br /&gt;  y, 0.0,&lt;br /&gt;  dx, 0.5,&lt;br /&gt;  dy: 0.5&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So this is a particle that is at position (0,0) and moves half a pixel to the right and bottom every time.&lt;br /&gt;&lt;br /&gt;Creating an array of these objects is of course really simple.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;function createParticle() {&lt;br /&gt;  return {&lt;br /&gt;    x: 0.0,&lt;br /&gt;    y, 0.0,&lt;br /&gt;    dx, 0.5,&lt;br /&gt;    dy: 0.5&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;var particles = [];&lt;br /&gt;for (var i=0; i &lt; 10; i++) {&lt;br /&gt;  particles.push(createParticle());&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;But of course these are really boring particles. They're all the same. Let's make the particles unique by adding a bit of randomness:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;function createParticle() {&lt;br /&gt;  return {&lt;br /&gt;    x: 0.0,&lt;br /&gt;    y, 0.0,&lt;br /&gt;    dx, Math.random() + 0.5,&lt;br /&gt;    dy: Math.randon() + 0.5&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;var particles = [];&lt;br /&gt;for (var i=0; i &lt; 10; i++) {&lt;br /&gt;  particles.push(crateParticle());&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So this creates 10 particles. They all start from the same location on the screen, yet move out in a different directory.&lt;br /&gt;&lt;br /&gt;With that out of the way, let's move on to the behavior of the particles.&lt;br /&gt;&lt;h2&gt;Particle behavior&lt;/h2&gt;&lt;br /&gt;As I said in the previous section, particles move in a predictable way. So we'll need a function that updates the position of each particle every frame.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;function update() {&lt;br /&gt;  for (var i=0; i &lt; particles.length; i++) {&lt;br /&gt;    particles[i].x = particles[i].x + particles[i].dx;&lt;br /&gt;    particles[i].y = particles[i].y + particles[i].dy;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And of course we'll need to call this function periodically:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;setInterval("update()", 50);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Calling the update function every 50ms results in a 20 frames per seconds. This will give us quite smooth animation, without being too taxing for todays browsers/PCs.&lt;br /&gt;&lt;h2&gt;lifetime management&lt;/h2&gt;&lt;br /&gt;We're almost done here. But if we would start this particle system now, the particles would just move away from each other indefinitely. That won't be very interesting to look at. &lt;br /&gt;&lt;br /&gt;What we need to add is some way for the particles to die and for new particles to be born. The simplest way to do this for now is by giving each particle a lifetime counter at birth and just decrease that with every update.&lt;br /&gt;&lt;br /&gt;Let's first update our createParticle function:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;function createParticle() {&lt;br /&gt;  return {&lt;br /&gt;    x: 0.0,&lt;br /&gt;    y, 0.0,&lt;br /&gt;    dx, Math.random() + 0.5,&lt;br /&gt;    dy: Math.random() + 0.5,&lt;br /&gt;    ttl: Math.floor(Math.random()*50) + 50 // time to live in frames&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We now defined that each particle will live between 50 and 100 frames. With a framerate of 20 frames per second, that will give the particles a lifetime of between 2.5 and 5 seconds.&lt;br /&gt;&lt;br /&gt;Let's use this new property in our update function:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;function update() {&lt;br /&gt;  for (var i=0; i &lt; particles.length; i++) {&lt;br /&gt;    particles[i].ttl = particles[i].ttl - 1;&lt;br /&gt;    if (particles[i].ttl &gt; 0) {&lt;br /&gt;      particles[i].x = particles[i].x + particles[i].dx;&lt;br /&gt;      particles[i].y = particles[i].y + particles[i].dy;&lt;br /&gt;    } else {&lt;br /&gt;      particles[i] = createParticle();&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;See the nice little trick we do here? We don't delete the particle, we just replace it with a new one. Of course the new particle will start at the origin again and will have its own direction and speed.&lt;br /&gt;&lt;br /&gt;This way we will always have the same number of particles in our system.&lt;br /&gt;&lt;h2&gt;visualize the particle system&lt;/h2&gt;&lt;br /&gt;I was not completely honest at the start here. There is actually one more step we have to take before we can see our particle system in action. After all what good is a particle system if you can't see it.&lt;br /&gt;&lt;br /&gt;Up until now the particle system we've created is completely agnostic to the output system. If you use JavaScript for scripting your DirectX game, you might render these particles onto a DirectDraw surface. If you feel really "Linuxy" you might render them onto a 24x40 character display. But for our purposes here, we'll render the particles using HTML.&lt;br /&gt;&lt;br /&gt;I'll just show the modified fragments here. For the complete script, refer to the end of the article.&lt;br /&gt;&lt;br /&gt;We visualize each particle as a simple HTML span with a blue border:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;function createParticle(span) {&lt;br /&gt;  return {&lt;br /&gt;   elm: span ? span : createParticleElement(),&lt;br /&gt;        ...&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;function createParticleElement() {&lt;br /&gt;  var elm = document.createElement('span');&lt;br /&gt;  elm.style.border = '1px solid blue';&lt;br /&gt;  elm.style.position = 'absolute';&lt;br /&gt;  elm.style.width = elm.style.height = '3px';&lt;br /&gt;  container.appendChild(elm);&lt;br /&gt;  return elm; &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;When updating the particle, we also have to update its span. When the particles dies, we re-use the span for the new particle:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    if (particles[i].ttl &gt; 0) {&lt;br /&gt;      ...&lt;br /&gt;      particles[i].elm.style.left = Math.floor(particles[i].x) + 'px';&lt;br /&gt;      particles[i].elm.style.top = Math.floor(particles[i].y) + 'px';&lt;br /&gt;    } else {&lt;br /&gt;      particles[i] = createParticle(particles[i].elm);&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;demo&lt;/h2&gt;&lt;br /&gt;The frame below shows a demonstration of the particle system we just built. If it doesn't work for you, click &lt;a href='http://apps.vanpuffelen.net/jsparticles/particles1.html' target='_blank'&gt;this link&lt;/a&gt; to open the same demo in a popup window.&lt;br /&gt;&lt;iframe src='http://apps.vanpuffelen.net/jsparticles/particles1.html' width='340' height='240'&gt; &lt;/iframe&gt;&lt;br /&gt;&lt;h2&gt;next steps&lt;/h2&gt;&lt;br /&gt;As you can see this particle system is about as simple as it can be. Real particle systems in games and simulations are of course much more complex. But the basic ingredients stay the same:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;an array of particle data structures&lt;/li&gt;&lt;br /&gt;&lt;li&gt;a function that updates all particles each frame&lt;/li&gt;&lt;br /&gt;&lt;li&gt;some form of lifetime management for the particles&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;If there is interest in it, I would like to make this post the first of a series where we investigate particle systems. If that is the case, the next step will be to make the particle system a bit more interesting by adding some "physics-like" behavior to it.&lt;br /&gt;&lt;h2&gt;the finished script&lt;/h2&gt;&lt;br /&gt;This is the complete script needed to run this particle system. If you count them, you'll see that it is only 47 lines of code. So you can create an (admittedly simple) particle system in less than 50 lines of code.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;var container;&lt;br /&gt;var particles;&lt;br /&gt;&lt;br /&gt;function createParticle(span) {&lt;br /&gt;  return {&lt;br /&gt;    elm: span ? span : createParticleElement(),&lt;br /&gt;    x: 0.0,&lt;br /&gt;    y: 0.0,&lt;br /&gt;    dx: Math.random() + 0.5,&lt;br /&gt;    dy: Math.random() + 0.5,&lt;br /&gt;    ttl: Math.floor(Math.random()*50) + 50 // time to live in frames&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function createParticleElement() {&lt;br /&gt;  var elm = document.createElement('span');&lt;br /&gt;  elm.style.border = '1px solid blue';&lt;br /&gt;  elm.style.position = 'absolute';&lt;br /&gt;  elm.style.width = elm.style.height = '3px';&lt;br /&gt;  container.appendChild(elm);&lt;br /&gt;  return elm; &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function update() {&lt;br /&gt;  for (var i=0; i &lt; particles.length; i++) {&lt;br /&gt;    if (i == 0) console.log(i+' '+particles[i].ttl);&lt;br /&gt;    particles[i].ttl = particles[i].ttl - 1;&lt;br /&gt;    if (particles[i].ttl &gt; 0) {&lt;br /&gt;      particles[i].x = particles[i].x + particles[i].dx;&lt;br /&gt;      particles[i].y = particles[i].y + particles[i].dy;&lt;br /&gt;      particles[i].elm.style.left = Math.floor(particles[i].x) + 'px';&lt;br /&gt;      particles[i].elm.style.top = Math.floor(particles[i].y) + 'px';&lt;br /&gt;    } else {&lt;br /&gt;      particles[i] = createParticle(particles[i].elm);&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;window.onload = function() {&lt;br /&gt;  container = document.getElementById('container');&lt;br /&gt;  particles = [];&lt;br /&gt;  for (var i=0; i &lt; 10; i++) {&lt;br /&gt;    particles.push(createParticle());&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  setInterval("update()", 50);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Download it &lt;a href='http://apps.vanpuffelen.net/jsparticles/particles1.js'&gt;here&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The corresponding HTML:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;html&gt;&lt;br /&gt;    &amp;lt;head&gt;&lt;br /&gt;        &amp;lt;title&gt;JavaScript Particle System&amp;lt;/title&gt;&lt;br /&gt;        &amp;lt;script type="text/javascript" src="particles.js"&gt; &amp;lt;/script&gt;&lt;br /&gt;    &amp;lt;/head&gt;&lt;br /&gt;    &amp;lt;body&gt;&lt;br /&gt;        &amp;lt;div id="container" style="width:320px; height: 200px"&gt; &amp;lt;/div&gt;&lt;br /&gt;    &amp;lt;/body&gt;&lt;br /&gt;&amp;lt;/html&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Download it &lt;a href='http://apps.vanpuffelen.net/jsparticles/particles1.html'&gt;here&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-5914405052816693922?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/5914405052816693922/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=5914405052816693922' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/5914405052816693922'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/5914405052816693922'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2009/05/particle-systems-in-javascript.html' title='Particle systems in JavaScript'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-3697962379980983151</id><published>2009-04-17T16:40:00.008+01:00</published><updated>2009-10-31T15:05:21.491+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mstsc remotedesktop rdc microsoft console admin'/><title type='text'>mstsc /console doesn't work anymore</title><content type='html'>&lt;div style="margin: 20px; border: 1px solid grey; background-color: silver; font-size: 150%"&gt;Summary: instead of calling &lt;code&gt;mstsc /console /v:servername&lt;/code&gt; you should now use &lt;code&gt;mstsc /admin /v:servername&lt;/code&gt;.&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;This post should really go under the category: what were they thinking?&lt;br /&gt;&lt;br /&gt;In our company we use a product that for licensing reasons really wants to make sure that only a single instance of the application is running. One of the things the developers have done to ensure this, is not allowing the application to be started on a remote desktop session. Otherwise: you could install the program on a single machine and let multiple users initiate remote sessions on that machine and this violate the license agreement. Now I'm not saying I agree with this type of licensing setup, but the software is pretty good and we want to make sure we're not doing anything illegal with it.&lt;br /&gt;&lt;br /&gt;So what we came up with was setting up a server with the software and then asking developers needing the tool to connect to the console session on that server using Windows' Remote Desktop Connection. The console session is (from the little that I understand of it) the first session that is started on a machine. So even on a terminal services server, there is only one console session. Connecting to it is quite simple from a command prompt:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;mstsc /console v:machinename&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The chances of two of our users needing access to the tool at the same time are pretty slim, so this setup actually worked for us quite well for the last year or so. Until last week that is.&lt;br /&gt;&lt;br /&gt;Suddenly I was getting reports from one of my team members that he couldn't start the tool due to a licensing error. He checked and double checked, but seemed quite sure that he was using the correct command line. I walked up to the server myself and tried to start the tool - it started without problems. So I left the tool running for him and told him to try again. He did... and said the tool wasn't there when he connected!&lt;br /&gt;&lt;br /&gt;Anyone with enough experience with multi-session machines knows that this means the remote desktop was in another session than the one I started the application in. But how could that be? My team member clearly told mstsc to connect to the console session.&lt;br /&gt;&lt;br /&gt;A few Google searches later, I found the answer. Now keep in mind: the problem occurred and was solved last week. But just writing about it here, still makes me angry - very angry with some people at Microsoft.&lt;br /&gt;&lt;br /&gt;What happened is that a team at Microsoft found out that there was &lt;a href="http://blogs.technet.com/askperf/archive/2007/04/27/application-compatibility-session-0-isolation.aspx"&gt;a security issue with the way terminal services worked&lt;/a&gt;. So they changed the logic of console vs. administrative sessions for security reasons. Nothing wrong with that - I appreciate security as much as the next guy.&lt;br /&gt;&lt;br /&gt;So they now separate the real session 0 from the first GUI session. Sounds good. I am no security expert so I don't know if it helps, but I'll take their word for it. But they didn't stop at that. Somebody then also decided that the /console option of the remote desktop client should not be called /console anymore. After all you might not be always be able to connect to the console anymore - on newer servers it would connect you to the first, typically administrative GUI. So they relabeled the "console" session to the "administrative" session. Relabeling is not wrong - I appreciate clarity as much at the next guy.&lt;br /&gt;&lt;br /&gt;So instead of calling &lt;code&gt;mstsc /console /v:servername&lt;/code&gt; you should now use &lt;code&gt;mstsc /admin /v:servername&lt;/code&gt;. All clear - nothing wrong with that.&lt;br /&gt;&lt;br /&gt;All of this so far is fine and can be explained to anyone. But this of course leaves one decision to be made: what do you do with the existing &lt;code&gt;/console&lt;/code&gt; option? What do you do with all those existing scripts that might already use &lt;code&gt;mstsc /console&lt;/code&gt; in the expectation that they end up connected to the console session?&lt;br /&gt;&lt;br /&gt;And this is where they did something that I don't understand, that I really, REALLY, REEEAAAALLLY don't understand. They decided to in some cases start ignoring the &lt;code&gt;/console&lt;/code&gt; option. So with some versions of the remote desktop client, using &lt;code&gt;mstsc /console&lt;/code&gt; will end you up in the console/admin session. And in other versions of the remote desktop client, using the same command will end you up in a completely normal session.&lt;br /&gt;&lt;br /&gt;Even after a week of thinking about it, I still don't understand why somebody would decide to silently start ignoring the &lt;code&gt;/console&lt;/code&gt; a command line switch that users might depend on. The switches of a command line tool are pretty much the API that you expose to the outside world. Whole build scripts have been built against command line tools based solely on the fact that the switches that are there will not change. Switches might be added, but nothing ever gets dropped. Except at Microsoft I guess. There some genius decides that in some cases they would start silently ignoring the &lt;code&gt;/console&lt;/code&gt; option on the command line. See this post on the &lt;a href="http://blogs.msdn.com/rds/archive/2007/12/17/changes-to-remote-administration-in-windows-server-2008.aspx"&gt;exact behavior in all cases&lt;/a&gt; or read this one for &lt;a href="http://blogs.technet.com/askperf/archive/2008/01/04/mstsc-exe-no-more-console-switch-in-rdc-6-1.aspx"&gt;non-Windows-2008 servers&lt;/a&gt;. Don't expect to understand the logic after that, but at least it is clear that they've spent a lot of time on this change. And after last week: so have I.&lt;br /&gt;&lt;br /&gt;I know not to expect an apologetic phone call from Redmond on this, nor do I really expect them to change the behavior of the mstsc client. Instead I'll just warn my fellow programmers and be on the lookout for remote desktop sessions that don't behave like I expect them to.&lt;br /&gt;&lt;br /&gt;Ok... time to breath in and breath out, count to ten and do my exercises. And get back to work of course. Now what was I working on?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-3697962379980983151?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/3697962379980983151/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=3697962379980983151' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/3697962379980983151'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/3697962379980983151'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2009/04/mstsc-console-doesnt-work-anymore.html' title='mstsc /console doesn&apos;t work anymore'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-5924414178481623667</id><published>2008-12-25T20:39:00.010+01:00</published><updated>2008-12-25T20:57:26.709+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='palm'/><category scheme='http://www.blogger.com/atom/ns#' term='gphone'/><category scheme='http://www.blogger.com/atom/ns#' term='googe'/><category scheme='http://www.blogger.com/atom/ns#' term='treo'/><category scheme='http://www.blogger.com/atom/ns#' term='iphone'/><title type='text'>Which keyboard is best on mobile phones?</title><content type='html'>It seems that mobile phones are the main place where UI experiments are still being done. One of the types of UI experiment that recently drew my attention was in keyboard design.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Palm Treo&lt;/h2&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_QCmUgDEhTiw/SVPj9nrA-7I/AAAAAAAACKo/HViAnE3oBww/s1600-h/DSC01913.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 239px;" src="http://4.bp.blogspot.com/_QCmUgDEhTiw/SVPj9nrA-7I/AAAAAAAACKo/HViAnE3oBww/s320/DSC01913.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5283817435596651442" /&gt;&lt;/a&gt;&lt;br /&gt;I just got a Palm Treo from work. That Treo has a built in QWERTY keyboard with horribly small keys. The keyboard is always available, together with the screen. And although that sounds convenient, it does mean that both the screen and the keyboard are small and the device is clunky.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;GPhone&lt;/h2&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_QCmUgDEhTiw/SVPkuA22lzI/AAAAAAAACKw/iV7d-PS9ki0/s1600-h/9808_1.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 252px;" src="http://4.bp.blogspot.com/_QCmUgDEhTiw/SVPkuA22lzI/AAAAAAAACKw/iV7d-PS9ki0/s320/9808_1.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5283818266990909234" /&gt;&lt;/a&gt;&lt;br /&gt;Compare this to the design of the first Google phone: the T-Mobile G1. When using this device as a phone the keyboard is stowed away under the screen. When you want to access one of its many PDA functions, you fold away the screen and the mini QWERTY keys become available. So while in phone mode the device is a lot smaller, but in PDA mode it is bigger.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;iPhone&lt;/h2&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_QCmUgDEhTiw/SVPlAIpN3CI/AAAAAAAACK4/PzrVFB5qhPM/s1600-h/apple-iphone-intelligent-keyboard-on-screen-demonstration%255B2%255D%5B1%5D"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 296px; height: 320px;" src="http://3.bp.blogspot.com/_QCmUgDEhTiw/SVPlAIpN3CI/AAAAAAAACK4/PzrVFB5qhPM/s320/apple-iphone-intelligent-keyboard-on-screen-demonstration%255B2%255D%5B1%5D" border="0" alt=""id="BLOGGER_PHOTO_ID_5283818578318842914" /&gt;&lt;/a&gt;&lt;br /&gt;Lastly there's the iPhone solution: a virtual on-screen keyboard. There on-screen keyboards were pioneered by Windows PDAs about a decade ago, but leave it to Apple to breathe stylish new life into an existing concept. The screen on your iPhone is big and can be used for normal operation. Until you need a keyboard, then the screen is split into two: the top section remains a normal screen and the bottom section becomes a virtual keyboard. The virtual keys are as miniaturized as their physical counterpart. But they do miss the tactile feedback you get from the other devices. So while it looks endlessly better, inputting text on the iPhone is probably slower than on the others.&lt;br /&gt;&lt;br /&gt;So what do you think? Which is the better way of inputting text on your mobile device? The Treo approach of an "always on" keyboard? The way the Google Phone hides the keyboard during normal operation? Or do you prefer the iPhone's virtual keyboard?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-5924414178481623667?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/5924414178481623667/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=5924414178481623667' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/5924414178481623667'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/5924414178481623667'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2008/12/which-keyboard-is-best-on-mobile-phones.html' title='Which keyboard is best on mobile phones?'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_QCmUgDEhTiw/SVPj9nrA-7I/AAAAAAAACKo/HViAnE3oBww/s72-c/DSC01913.JPG' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-2958807689212097729</id><published>2008-10-25T09:12:00.002+01:00</published><updated>2008-10-25T09:19:00.016+01:00</updated><title type='text'>Optimistic locking vs. pessimistic locking</title><content type='html'>If you have an education in computer science you typically learned about locking in databases. I was taught that the only safe way to update a database in a multi-user environment is to use a transaction:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;  &lt;li&gt;acquire a lock on the record&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;read the current value from the record&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;calculate the new value&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;write the new value to the record&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;release the lock&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;Simple. Reliable. Safe.&lt;br /&gt;&lt;br /&gt;And horribly slow...&lt;br /&gt;&lt;br /&gt;Imagine thousands of users concurrently hammering your database. Most of these users will be working on different records. So for most of them, the lock is completely useless. But you're still paying the price for acquiring this lock on every database update. That is why this type of locking is called a pessimistic lock. You assume that multiple people will want to update that same record and prevent them from doing so. &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://tbn0.google.com/images?q=tbn:k46I8BklSANLWM:http://p-stat.livejournal.com/img/mood/sam/pessimistic.gif"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 50px; height: 50px;" src="http://tbn0.google.com/images?q=tbn:k46I8BklSANLWM:http://p-stat.livejournal.com/img/mood/sam/pessimistic.gif" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I thought this was the only way to do locking, until I encountered something completely different about 10 years ago. At my company at the time we used an internally built software application to track stock levels of our products. And that application used the pessimistic lock pattern described above. The application worked fine for some time, but as it got more users it started to have more and more performance problems. I was brought in as part of a team to solve those problems. I immediately went to work on some of the heaviest operations and was able to bring startup performance back to acceptable levels in a few days. But the updates to the database were still going as slow as ever.&lt;br /&gt;&lt;br /&gt;That's when the technical lead of my team came with an interesting suggestion. "Why don't we remove the database locks?" Of course the entire team was in shock. You couldn't remove the locks from the updates. That meant that the data could become corrupt. That is what we told the tech lead: you need locks to keep your data from becoming corrupt. "So why don't we ensure that the data won't become corrupt when we update it?"&lt;br /&gt;&lt;br /&gt;Nobody understood what he meant. Keep in mind that all of us were brought up with the concept of pessimistic locking. So the lead asked us to show him the queries that were being fired against the database:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;  &lt;li&gt;SELECT ID, StockCount FROM tblProducts WHERE ID=?&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;application code calculates the new stock for the product&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;UPDATE tblProducts SET StockCount=? WHERE ID=?&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;He thought about it for a while and then said "How about this?":&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;  &lt;li&gt;SELECT ID, StockCount FROM tblProducts WHERE ID=?&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;application code calculates the new stock for the product&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;UPDATE tblProducts SET StockCount=? WHERE ID=? AND StockCount=?&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;"So we pass in both the new value for StockCount and the value that we expect to be there. That way the update will only be performed when the StockCount has not been modified by anyone else."&lt;br /&gt;&lt;br /&gt;I can still remember the shock going through my mind. Does this mean that I don't need to lock the record before reading it? What will we do when the StockCount &lt;u&gt;did&lt;/u&gt; actually change between our SELECT and our UPDATE?&lt;br /&gt;&lt;br /&gt;All these questions were answered in the next few hours. If some other user had performed a conflicting UPDATE while our code was busy figuring out the new stock vale, we'd have to reperform the entire sequence above. So the process turned more into a loop:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;while the stock value has not been updated:&lt;/li&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;  &lt;li&gt;SELECT ID, StockCount FROM tblProducts WHERE ID=?&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;application code calculates the new stock for the product&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;UPDATE tblProducts SET StockCount=? WHERE ID=? AND StockCount=?&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;And sure the code got a bit more complex. And definitely in the worst case, this code will send a lot more queries to the database than the one with pessimistic locking. After all, it will fire multiple SELECT and UPDATE statements. Potentially an unlimited number of them, because of the loop.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://tbn0.google.com/images?q=tbn:uARDwqVhjhXRsM:http://p-stat.livejournal.com/img/mood/sam/optimistic.gif"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 50px; height: 50px;" src="http://tbn0.google.com/images?q=tbn:uARDwqVhjhXRsM:http://p-stat.livejournal.com/img/mood/sam/optimistic.gif" border="0" alt="" /&gt;&lt;/a&gt;But that is exactly why people call this the optimistic locking pattern. As I said earlier, most concurrent updates to a database are to different records. So most updates don't have a conflict. But with pessimistic locking all updates pay the price of acquiring a lock. So all updates pay get a penalty for a small fraction of them that do require the locking.&lt;br /&gt;&lt;br /&gt;Optimistic locking is built on the assumption that most updates are without conflicts. When there is no conflict, there is no penalty. And only when there has been a conflicting update, do we go into the loop of retrying and pay the penalty. The penalty will be much higher than with pessimistic locking, but it will only be paid for the cases where it is needed.&lt;br /&gt;&lt;br /&gt;So there you have it, the difference between optimistic and pessimistic locking and how I learned about them. Pick whichever one you think will work best for your project. Both of them are perfectly suitable for keeping your data safe. It all depends on the ratio of "updates with conflicts" vs. "updates without conflicts" for your application.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-2958807689212097729?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/2958807689212097729/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=2958807689212097729' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/2958807689212097729'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/2958807689212097729'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2008/10/optimistic-locking-vs-pessimistic.html' title='Optimistic locking vs. pessimistic locking'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-5252139055185275158</id><published>2008-07-13T09:53:00.000+01:00</published><updated>2008-07-13T08:56:11.810+01:00</updated><title type='text'>Using PageRank to determine the most powerful people on LinkedIn</title><content type='html'>Last week I was reading &lt;a href="http://dbpubs.stanford.edu:8090/pub/1999-66"&gt;the original paper by Larry Page and Sergey Brin on the PageRank algorithm&lt;/a&gt;. &lt;a href="http://en.wikipedia.org/wiki/PageRank"&gt;PageRank&lt;/a&gt; is an algorithm to determine the relative authority of pages that they came up with while at Stanford University. And it is of course also the algorithm they then just started using for a search engine at Stanford - a search engine they named Google.&lt;br /&gt;&lt;br /&gt;If you just skip the math for a moment, the PageRank algorithm is surprisingly simple. The basic rules in determining the rank of a page are:&lt;br /&gt;1) the links to a page determine its rank&lt;br /&gt;2) a page spreads its rank over the links it contains&lt;br /&gt;&lt;br /&gt;So if you have a page with three links on it, that page will contribute one third of its rank to each of those links. A page with a rank of 100 that has two links on it, will give 50 points to each of its targets.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_QCmUgDEhTiw/SHmxCP-lRbI/AAAAAAAAA6g/aDHgNpW4558/s1600-h/simplified+pagerank.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_QCmUgDEhTiw/SHmxCP-lRbI/AAAAAAAAA6g/aDHgNpW4558/s320/simplified+pagerank.png" border="0" alt="A simplified PageRank calculation, showing that each page re-distributes its own PageRank over the pages it links to" id="BLOGGER_PHOTO_ID_5222399895119676850" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;One thing you may already have noticed is that the definition of the PageRank can't be determined in one go: you need to know the value of all incoming links to determine the rank of a page, which you need to know to determine the value of all outgoing links. According to the paper you can simply calculate all PageRanks in an iterative fashion until it converges after a quite limited number of steps. I'll gladly take their word for it, since that puts the algorithm back into the hands of us programmers. We can iterate over large collections of data like no other.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_QCmUgDEhTiw/SHmxCSKEc-I/AAAAAAAAA6o/BW8Lq_kwgt8/s1600-h/simplified+converged+pagerank.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_QCmUgDEhTiw/SHmxCSKEc-I/AAAAAAAAA6o/BW8Lq_kwgt8/s320/simplified+converged+pagerank.png" border="0" alt="A converged PageRank calculation, showing that repeated refinement will result in a stable network of PageRanks" id="BLOGGER_PHOTO_ID_5222399895704728546" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The PageRank algorithm is so simple that it not only works, it has turned the search engine world (and the internet in general) upside down. I'm quite sure that today's PageRank is determined in a much more complex fashion, but let's stick to this older and more simple model for now.&lt;br /&gt;&lt;br /&gt;What else could we use PageRank for?&lt;br /&gt;&lt;br /&gt;What would happen if you let PageRank loose on a completely different type of data? How about instead of taking all the hyperlinked documents on the web, we take a social network? So take the network of people at a site like LinkedIn, Facebook, Plaxo or Orkut and apply the same two basic rules:&lt;br /&gt;1) a person's rank is determined by the incoming links&lt;br /&gt;2) a person's rank is evenly divided over their outgoing links&lt;br /&gt;&lt;br /&gt;So the more people link to you, the higher your rank. And if more people link to the people that link to you, your rank will get even higher.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_QCmUgDEhTiw/SHm0mL66N5I/AAAAAAAAA68/A6AII6qoVXY/s1600-h/576632144_54192779fe%5B1%5D.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://2.bp.blogspot.com/_QCmUgDEhTiw/SHm0mL66N5I/AAAAAAAAA68/A6AII6qoVXY/s320/576632144_54192779fe%5B1%5D.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5222403811040704402" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;If we let PageRank loose on a social network like this, what will happen? Who will get the highest PageRank? And what does the rank mean in such a scenario? Is your PageRank an indicator of the power of your social network? Will my boss indeed have a higher rank than me, as you would expect? And will his boss have an even higher rank?&lt;br /&gt;&lt;br /&gt;There are of course differences between links on a social network site and the web in general. For example: in a social network like this the links between people are non-directed, when you are connected to someone that person is also connected to you. This will likely affect the results, but I am too bad at maths to foresee how.&lt;br /&gt;&lt;br /&gt;Basically I have no idea what the outcome of such an experiment will be. It just seems to me that it would be incredibly interesting to see what the numbers are and then determine what they mean. Much like Page and Brin did with their original PageRank: first let the algorithm loose on the data, then see what you can do with the results. If the algorithm feels intuitively good, the results are bound to be interesting.&lt;br /&gt;&lt;br /&gt;Does you feel the same? Can this be an interesting experiment? Or is this complete nonsense and is PageRank not applicable to this scenario? And do the social network sites have APIs that will allow you to do this? Or is your data access limited to the people in your own network (as they always promise in their privacy statement) and can only someone working for Facebook/LinkedIn/Plaxo create such an application?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-5252139055185275158?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/5252139055185275158/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=5252139055185275158' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/5252139055185275158'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/5252139055185275158'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2008/07/using-pagerank-to-determine-most.html' title='Using PageRank to determine the most powerful people on LinkedIn'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_QCmUgDEhTiw/SHmxCP-lRbI/AAAAAAAAA6g/aDHgNpW4558/s72-c/simplified+pagerank.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-3664408723800984522</id><published>2008-03-22T09:45:00.003+01:00</published><updated>2008-03-22T10:04:17.475+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='product'/><category scheme='http://www.blogger.com/atom/ns#' term='project management'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Three ingredients to a better bug report</title><content type='html'>At work I've recently been going through lots of bug reports. &lt;img style="float:right; margin:0 0 10px 10px;" src="http://3.bp.blogspot.com/_QCmUgDEhTiw/R-TKUIckRnI/AAAAAAAAA48/x2KCaCPdftE/s320/bug.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5180487918596408946" /&gt; It is part of my job to determine the priority of each defect that gets reported and whether we should still fix it in the upcoming release. With multiple testers trying to break the product in all possible ways and a deadline approaching rapidly, analyzing the newfound defects seems to take more and more time.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;What did you see?&lt;/h3&gt;&lt;br /&gt;People entering defects can actually do a lot to reduce the time it takes to analyze their entries. You'd be amazed at the number of defects that say something along the lines:&lt;br /&gt;&lt;ul style="list-style-type: none"&gt;&lt;li&gt;&lt;br /&gt;    "when I do A, B happens"&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;There are of course cases when this is all there is to say about a defect. For example:&lt;br /&gt;&lt;ul style="list-style-type: none"&gt;&lt;li&gt;&lt;br /&gt;    "when I click the Ok button, the browser shows an internal apache error"&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Granted, it would be more useful if the report said a bit more about the error message. But it is at least clear that an internal error message should not be shown to the user.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;What did you expect to see?&lt;/h3&gt;&lt;br /&gt;Unfortunately things are not always so clear:&lt;br /&gt;&lt;ul style="list-style-type: none"&gt;&lt;li&gt;&lt;br /&gt;    "when try to I delete the last item from the list, I get a message saying at least one item is required"&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;When I get an error report like this, I'm not sure what to do with it. &lt;img style="float:right; margin:0 0 10px 10px;" src="http://3.bp.blogspot.com/_QCmUgDEhTiw/R-TK7IckRoI/AAAAAAAAA5E/uvJSlh9yRqk/s320/report.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5180488588611307138" /&gt; Most likely there is an internal rule in the program that this list may never be empty. And the program seems to enforce this rule by giving a message when you try to delete the last remaining item. So there is a "business rule" in the program and the developer wrote code to enforce that rule. Where is the defect?&lt;br /&gt;&lt;br /&gt;In cases like these I ask the person who entered the defect why they think this behavior is wrong. Typically I get an answer like:&lt;br /&gt;&lt;ul style="list-style-type: none"&gt;&lt;li&gt;&lt;br /&gt;    "if I can't delete the selected item, the delete button should be disabled"&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;This added information makes things a lot clearer. So the tester didn't disagree with the fact that there should always be at least one item in the list, they just didn't agree with the way it was handled.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Why do you think your expectation is better than the current behavior?&lt;/h3&gt;&lt;br /&gt;But the above leaves me with a problem. There is a clear rule in the program that the list must never be empty. The programmer implemented this one way, someone else thought it should have been implemented another way.&lt;br /&gt;&lt;br /&gt;In cases like these I ask the tester (or whoever reported the defect) to explain why they think their expectation is better than the current behavior. In the example we've used so far, the reason could be something like:&lt;br /&gt;&lt;ul style="list-style-type: none"&gt;&lt;li&gt;&lt;br /&gt;    "clicking the button when there is only one item in the list will always show an error message - the delete action will never be performed. Buttons that don't lead to an action being executed should be disabled."&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;This is a clear - albeit somewhat abstract - description of the reason why the person expected the behavior to be different. &lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Prior art&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;In this example I doubt whether anyone will disagree with the reasoning of the tester. But there are many cases where someone will disagree. Especially the developer that implemented the functionality will tend to defend the way it works.&lt;br /&gt;&lt;br /&gt;That's why I normally prefer the defect to point to other places where similar functionality is available in the way the tester prefers it. So in the example defect:&lt;br /&gt;&lt;ul style="list-style-type: none"&gt;&lt;li&gt;&lt;br /&gt;    "in screens B and C we have a similar list and there the delete button is disabled if there is only one item remaining in the list"&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;This type of argument works especially well when the functionality in screens B and C has already been in a released version of the product. The users of the product have experienced the functionality and they will expect the new screen to behave in the same way.&lt;br /&gt;&lt;br /&gt;If no similar functionality is available in the application, I often look for other programs that have similar functionality. On Windows the notepad application is one of my favorite examples. Everybody has it and the functionality as not substantially changed for at least a decade. Of course the functionality your program has might not be in notepad. In those cases I often refer to programs like Microsoft Office, Outlook, Firefox or the Google home-page. Not because I think these are perfect programs, but because they're so ubiquitous that most users accept them as a reference point for the behavior they expose.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Summary&lt;/h3&gt;&lt;br /&gt;So a bug report should at least contain the following ingredients:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;What did you see?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;What did you expect to see?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Why do you think that 2 is better than 1?&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;img style="float:right; margin:0 0 10px 10px;" src="http://2.bp.blogspot.com/_QCmUgDEhTiw/R-TLX4ckRpI/AAAAAAAAA5M/hO6PneXb2uA/s320/fix.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5180489082532546194" /&gt; Now if everyone starts filing their bug reports like that, I will have to spend a lot less time on analyzing them and can get back to fixing those defects sooner. Who knows... maybe we'll make that deadline after all.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-3664408723800984522?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/3664408723800984522/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=3664408723800984522' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/3664408723800984522'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/3664408723800984522'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2008/03/three-ingredients-to-better-bug-report.html' title='Three ingredients to a better bug report'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_QCmUgDEhTiw/R-TKUIckRnI/AAAAAAAAA48/x2KCaCPdftE/s72-c/bug.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-7413510384057067116</id><published>2008-03-02T09:19:00.000+01:00</published><updated>2008-03-02T09:23:25.066+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='Cob web'/><category scheme='http://www.blogger.com/atom/ns#' term='mesh'/><category scheme='http://www.blogger.com/atom/ns#' term='fork/join'/><category scheme='http://www.blogger.com/atom/ns#' term='Map/Reduce'/><category scheme='http://www.blogger.com/atom/ns#' term='grid'/><category scheme='http://www.blogger.com/atom/ns#' term='Hadoop'/><title type='text'>Finding primes using a grid of browsers</title><content type='html'>It's been a few months since I talked about my idea of &lt;a href="http://frank.vanpuffelen.net/2007/12/grid-computing-using-web-browsers.html"&gt;combining the power of a lot of browsers into a so-called computing grid&lt;/a&gt;. Since writing that post I've been working on such a browser based grid. Progress has been slow, but steady. And I'm learning a lot in the process, so I'm not complaining at all.&lt;br /&gt;&lt;br /&gt;My current prototype grid software has a server-side and a client-side part. Tasks and results get distributed over clients as needed. It's actually pretty cool to see one node start a task and another node finish it. But one thing that my grid lacks is a data storage service. Until I add such a service (in a scalable way) I am stuck with a grid that can only handle tasks like finding prime numbers. But even from such a classic task, there is still a lot to be learned.&lt;br /&gt;&lt;br /&gt;For example one of the problems we'll need to solve for a browser based grid is the fact that the nodes in the grid are not dedicated to being part of the grid. Whereas people that download &lt;a href="http://setiathome.berkeley.edu/"&gt;SETI@home&lt;/a&gt; at least consciously decide to contribute their computer cycles to the project, for a browser based grid this decision is less explicit. Of course this is part of the beauty of a using the web for this grid: it becomes very easy for people to join the grid. But it also means that we can't do certain things. Like eat up all available CPU cycles...&lt;br /&gt;&lt;br /&gt;So how do we find prime numbers without eating up too much CPU time? As you probably recall from school: a prime number is a number that can only be divided by 1 and by itself. So the simplest possible algorithm to determine if a given number is a prime is something like:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    function IsPrimeNumber(number) {&lt;br /&gt;        for (var i=2; i &lt; number-1; i++) {&lt;br /&gt;            if (number % i == 0) {&lt;br /&gt;                return false;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        return true;&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As you can see this is quite a literal translation of the definition of primes we all learned. And although there are plenty of optimizations you can do to this algorithm, we'll stick to to this very simple version for now.&lt;br /&gt;&lt;br /&gt;What happens if we execute this function on a node? It'll work fine for small numbers. But once the number goes beyond a certain threshold, the loop will start eating up quite some time. And since executing JavaScript in the browser is unrestricted but single-threaded, this means the script will eat up all CPU cycles for a noticeable amount of time. As I said before, that is something that I simply don't want to happen on this grid.&lt;br /&gt;&lt;br /&gt;So we need to modify our algorithm to not eat up all CPU time. We could optimize it by for example skipping all even numbers above 2. But that would only double the maximum number we can check with this function before the user starts to notice. What we need is something that will make this function work without noticeable effect on the CPU for any number.&lt;br /&gt;&lt;br /&gt;My approach to this has been to split the task into many sub-tasks. So instead of having one loop like in the version above, we split it into multiple loops that each run over a smaller range. Let's say we have the following helper function:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    function IsDividableBySomethingInRange(number, start, end);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This function will return &lt;code&gt;true&lt;/code&gt; if &lt;code&gt;number&lt;/code&gt; can be divided by one of the numbers in the range &lt;code&gt;[start, end&gt;&lt;/code&gt;. With this helper function, we can rewrite our original &lt;code&gt;IsPrimeNumber&lt;/code&gt; function to use ranges.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    function IsPrimeNumber(number) {&lt;br /&gt;        var CHUNK_SIZE = 1000;&lt;br /&gt;        for (var i=0; i &lt; number; i += CHUNK_SIZE) {&lt;br /&gt;            if (IsDividableBySomethingInRange(number, Math.max(i, 2), Math.min(i + CHUNK_SIZE, number-1)));&lt;br /&gt;                return false;&lt;br /&gt;            }&lt;br /&gt; }&lt;br /&gt;        return true;&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now if we leave the algorithm like this, we are still executing the work in one go. To actually remove that problem we need to convert the &lt;code&gt;IsDividableBySomethingInRange&lt;/code&gt; calls into sub-tasks, which can be executed on the grid separately. The &lt;code&gt;IsPrimeNumber&lt;/code&gt; function/task then just has to wait until all its sub-tasks have completed. &lt;br /&gt;&lt;br /&gt;The splitting of a task into sub-tasks that can be executed separately and independent of each other is a typical fork operation that I learned back in my college days. We would fork off some work to separate sub-threads, so it could be completed in parallel instead of executing each one in turn. Waiting for the sub-tasks is called a join operation. By creating a fork/join algorithm we're not just making task execution more deterministic, we're also improving parallelism by allowing the sub-tasks to run independently from each other.&lt;br /&gt;&lt;br /&gt;So what we really wanted to create all along is a fork/join version of our &lt;code&gt;IsPrimeNumber&lt;/code&gt; function/task.&lt;br /&gt;&lt;br /&gt;Let's say that we have a &lt;code&gt;fork&lt;/code&gt; operation that we can call to somehow fork off a function into a separate sub-task. And let's say that whenever such a sub-task is completed, it will call back into the original task with the result:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class IsPrimeNumber {&lt;br /&gt;    var number;&lt;br /&gt;    var result;&lt;br /&gt;    function IsPrimeNumber(number) {&lt;br /&gt;        this.number = number;&lt;br /&gt;        this.result = true;&lt;br /&gt;    }&lt;br /&gt;    function forkSubTasks() {&lt;br /&gt;        var CHUNK_SIZE = 1000;&lt;br /&gt;        for (var i=0; i &lt; number; i += CHUNK_SIZE) {&lt;br /&gt;            fork IsDividableBySomethingInRange(number, Math.max(i, 2), Math.min(i + CHUNK_SIZE, number-1)));&lt;br /&gt; }&lt;br /&gt;    }&lt;br /&gt;    function joinSubTask(subtask) {&lt;br /&gt;        if (subtask.result == true) {&lt;br /&gt;            this.result = false;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is a grid-enabled fork/join version of our &lt;code&gt;IsPrimeNumber&lt;/code&gt; task. When we execute this task with a very large number on a grid of nodes, the task will be forked into sub-tasks and each of those sub-tasks can be executed on any of the nodes. When a sub-task is completed its result can be joined back into the &lt;code&gt;IsPrimeNumber&lt;/code&gt;, which will assure that the combined result is correct.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;IsPrimeNumber(2137)&lt;br /&gt;        forkSubTasks&lt;br /&gt;             |&lt;br /&gt;             |---------&gt;IsDividableBySomethingInRange(2137, 2, 1000)&lt;br /&gt;             |                  |&lt;br /&gt;             |------------------+--------------&gt;IsDividableBySomethingInRange(2137, 1000, 2000)&lt;br /&gt;             |                  |                     |&lt;br /&gt;             |------------------+---------------------+----------------&gt;IsDividableBySomethingInRange(2137, 2000, 2136)&lt;br /&gt;                                |                     |                       |&lt;br /&gt;        joinSubTask             |                     |                       |&lt;br /&gt;             |                  |                     |                       |&lt;br /&gt;             |&lt;-----------------+                     |                       |&lt;br /&gt;             |                                        |                       |&lt;br /&gt;             |&lt;---------------------------------------+                       |&lt;br /&gt;             |                                                                |&lt;br /&gt;             |&lt;---------------------------------------------------------------+&lt;br /&gt;             |&lt;br /&gt;true&lt;--------+&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If you know a bit about Google's &lt;a href="http://labs.google.com/papers/mapreduce.html"&gt;Map/Reduce&lt;/a&gt; algorithm (or the open-source &lt;a href="http://hadoop.apache.org/"&gt;Hadoop&lt;/a&gt; implementation of it) you will probably see the similarities between &lt;code&gt;join&lt;/code&gt; and &lt;code&gt;reduce&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;There is still many things that we can improve here (not swamping the grid with all sub-tasks at once, keep track of the number of missing sub-tasks, etc). But essentially we now have a grid-enabled way of determining whether any given number is a prime.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-7413510384057067116?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/7413510384057067116/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=7413510384057067116' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/7413510384057067116'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/7413510384057067116'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2008/03/finding-primes-using-grid-of-browsers.html' title='Finding primes using a grid of browsers'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-8920778071306556947</id><published>2008-02-02T10:43:00.000+01:00</published><updated>2008-02-02T10:51:35.155+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='velocity'/><category scheme='http://www.blogger.com/atom/ns#' term='project management'/><category scheme='http://www.blogger.com/atom/ns#' term='Scrum'/><title type='text'>Scrum: story points, ideal man days, real man weeks</title><content type='html'>My team completed its seventh sprint of a project. Once again all stories were accepted by the product owner.&lt;br /&gt;&lt;br /&gt;While one of the team member was giving the sprint demo, I started looking in more detail at some of the numbers. Because with seven sprints behind us, we've gathered quite some data on the progress from sprint to sprint. That's the velocity, for XP practitioners.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Looking at the data&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;So far we've had 131 story points of functionality accepted by the product owner, so that's an average of 18-19 per sprint. The distribution has not really been all that stable though. Here's a chart showing the number of accepted story points per sprint:&lt;br /&gt;&lt;img src="http://chart.apis.google.com/chart?cht=bvs&amp;chd=s:ecigmyy&amp;chs=320x200&amp;chbh=40,4&amp;chxt=x,y&amp;chxl=0:|1|2|3|4|5|6|7|1:|0|5|10|15|20|25|30" title="Accepted story points per sprint" /&gt;&lt;br /&gt;&lt;br /&gt;Although it is a bit difficult to see the trend in sprint 1 to 5, we seemed to be going slightly upward. This is in line with what you'd expect in any project: as the team gets more used to the project and to each other, performance increases a bit. &lt;br /&gt;&lt;br /&gt;The jump from sprint 5 to sprint 6 however is very clearly visible. This jump should come as no surprise when I tell you that our team was expanded from 3 developers to 5 developers in sprint 6 and 7. And as you can clearly see, those additional developers were contributing to the team velocity right from the start.&lt;br /&gt;&lt;br /&gt;But how much does each developer contribute? To see that we divide the number of accepted story points per sprint by the number of developers in that sprint:&lt;br /&gt;&lt;img src="http://chart.apis.google.com/chart?cht=bvs&amp;chd=s:ecigmee&amp;chs=320x200&amp;chbh=40,4&amp;chxt=x,y&amp;chxl=0:|1|2|3|4|5|6|7|1:|0|5|10" title="Accepted story points per developer per sprint" /&gt;&lt;br /&gt;Apparently we've been pretty consistently been implementing 5 story points per developer per sprint. There was a slight drop in sprint 6, which is also fairly typical when you add more developers to a project. But overall you can say that our velocity per developer has been pretty stable.&lt;br /&gt;&lt;br /&gt;Given this stability it suddenly becomes a simple (but still interesting) exercise to try and project when the project will be completed. All you need in addition to the data from the previous sprints, is an indication of the total estimate of all stories on the product backlog. We've been keeping track of that number too, so plotting both the work completed vs. the total scope gives the following chart:&lt;br /&gt;&lt;img src="http://chart.apis.google.com/chart?cht=lc&amp;chs=320x200&amp;chd=s:nnilooxx,AFKPVbjs&amp;chco=cc0000,00aa00&amp;chdl=scope|completed&amp;chxt=x&amp;chxl=0:|0|1|2|3|4|5|6|7" title="project burnup" /&gt;&lt;br /&gt;So it looks like we indeed will be finished with the project after one more sprint. That is of course, if the product owner doesn't all of a sudden change the scope. Or we find out that our initial estimates for the remaining stories were way off. After all: it's an agile project, so anything can happen.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Story points vs. ideal man days vs. real man weeks&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Whenever I talk about this "number of story points per developer per sprint" to people on other projects, they inevitably ask the same question. What is a story point? The correct Scrum answer would be that it doesn't matter what unit it is. It's a story point and we do about five story points per developer per sprint.&lt;br /&gt;&lt;br /&gt;But of course there is a different unit behind the story points. When our team estimates its stories, we ask ourselves the question: if I were locked into a room with no phone or other disturbances and a perfect development setup, after how many days would I have this story completed? So a story point is a so-called "ideal man day".&lt;br /&gt;&lt;br /&gt;From the results so far we can see that apparently this is a pretty stable way to estimate the work required. And stability is most important, way more important than for example absolute correctness. &lt;br /&gt;&lt;br /&gt;A classic project manager might take the estimate of the team (in ideal man days) and divide that by 5 to get to the ideal man weeks. Then divide by the number of people in the team to get to the number of weeks it should take the team to complete the work. And of course they'll add some time to the plan for "overhead", being the benevolent leaders that they are. This will give them a "realistic" deadline for the project. A deadline that somehow is never made, much to the surprise and outrage of the classic project manager.&lt;br /&gt;&lt;br /&gt;I'm just a Scrum master on the project. So I don't set deadlines. And I don't get to be outraged when we don't make the deadline. All I can do is study the numbers and see what it tells me. And what it tells me for the current project is that the number are pretty stable. And that's the way I like it.&lt;br /&gt;&lt;br /&gt;But there is a bit more you can do with the numbers. If you know that the developers in the team estimate in "ideal man days", you can also determine how many ideal man days fit into a real week. For that you need to know the length of a sprint.&lt;br /&gt;&lt;br /&gt;Our team has settled on a sprint length of four weeks. That's the end-to-end time between the sprints. So four weeks after the end of sprint 3, we are at the end of sprint 4. In those four weeks, we have two "slack days". One of those is for the acceptance test and demo. The other is for the retro and planning of the next sprint.&lt;br /&gt;&lt;br /&gt;So there is two days of overhead per sprint. But there is a lot more overhead during the sprint, so in calculations that span multiple sprints I tend to think of those two days as part of the sprint. &lt;br /&gt;&lt;br /&gt;So a sprint is simply four weeks. And in a sprint a developer on average completes 5 story points, which is just another way of saying 5 ideal man days. So in a real week there is 1.25 ideal man days!&lt;br /&gt;&lt;br /&gt;I just hope that our managers don't read this post. Because their initial reaction will be: "What? What are you doing the rest of the time? Is there any way we can improve this number? Can't you people just work harder?"&lt;br /&gt;&lt;br /&gt;Like I said before: I don't believe in that logic. It's classic utilization-focused project management. It suggests that you should try to have perfect estimates and account for all variables so that you can come to a guaranteed delivery date. The problem with that is that it doesn't work! If there's anything that decades of software engineering management should have taught us, is that there are too many unknown factors to get any kind of certainty on the deadline. So until we get more control of those variables, I'd much rather have a &lt;a href="http://frank.vanpuffelen.net/2007/08/scrum-utilization-vs-velocity.html"&gt;stable velocity than a high utilization&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-8920778071306556947?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/8920778071306556947/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=8920778071306556947' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/8920778071306556947'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/8920778071306556947'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2008/02/scrum-story-points-ideal-man-days-real.html' title='Scrum: story points, ideal man days, real man weeks'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-8643517708230073646</id><published>2008-01-20T09:42:00.000+01:00</published><updated>2008-01-20T09:43:26.954+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Cob web'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='Mac'/><category scheme='http://www.blogger.com/atom/ns#' term='Panic'/><category scheme='http://www.blogger.com/atom/ns#' term='Coda'/><category scheme='http://www.blogger.com/atom/ns#' term='Transmit'/><title type='text'>A WinSCP replacement for the Mac</title><content type='html'>About half a year ago I wrote a piece on the inability to use my iMac as a web development machine. The reason was very simple: the lack of a utility on OSX with a feature set similar to WinSCP on Windows. &lt;img style="float:right; margin:0 0 10px  10px;" src="http://2.bp.blogspot.com/_QCmUgDEhTiw/Rqw2c9A8y2I/AAAAAAAAAUQ/5T35rtllifo/s320/winscp%5B1%5D.png" /&gt; It doesn't need to have all features of WinSCP, but at least being able to browse a remote SSH/SCP file system as if it is local and being able to edit remote files without being bothered too much by the latency and remote-ness are a must-have for me.&lt;br /&gt;&lt;br /&gt;Apparently I'm not the only one looking for something to replace good old WinSCP on OSX, as this post is one of the most viewed and most commented on this site. In the comments visitors suggested many replacements: Cyberduck, Fugu, Smultron, Filezilla and Transmit. I tried all of them. &lt;br /&gt;&lt;br /&gt;&lt;img style="float:right; margin:0 0 10px 10px;" src="http://4.bp.blogspot.com/_QCmUgDEhTiw/R5MFg_u_n9I/AAAAAAAAA3w/Nv2T_H2CDS0/s320/PanicTransmit.gif"  border="0" /&gt; But none of these tools seem to do what I want, as -until last week- I still found myself at my XP laptop whenever I wanted to work on a site. Until last week? Yes, because about a week ago my trial license of Transmit ran out. And although it's no WinSCP replacement, Transmit is a decent SCP program. So I went to &lt;a href="http://www.panic.com/"&gt;the vendor website&lt;/a&gt; to see what it would cost. On the website I ran into their "One-window web development" program, called Coda. After all the tries one more couldn't hurt, so I decided to give it a go.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;" src="http://3.bp.blogspot.com/_QCmUgDEhTiw/R5MFtvu_n-I/AAAAAAAAA34/YnsQKz9IdN8/s320/PanicCoda.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5157472281735176162" /&gt;&lt;br /&gt;After installing Coda you have to get used to the fact that it is not a WinSCP clone. So it doesn't have a dual pane Norton Commander like interface, but instead just has the list of remote files on the left hand side. And when you open one of those files -instead of getting the WinSCP editor popup- Coda opens the files in a large panel on the right hand side. Open a second file and it adds a tab on that panel, where WinSCP would open a second popup window.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_QCmUgDEhTiw/R5MDK_u_n8I/AAAAAAAAA3o/AlyG_TuGsVQ/s1600-h/sites-screenshot_03%5B1%5D.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://4.bp.blogspot.com/_QCmUgDEhTiw/R5MDK_u_n8I/AAAAAAAAA3o/AlyG_TuGsVQ/s320/sites-screenshot_03%5B1%5D.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5157469485711466434" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Looking at it like that, it seems like Coda tries to mimic an IDE more than anything else. But then an IDE for remote web site development, which is exactly what I use it for. I'm now about half-way through my trial period for Coda and I must say I love it so far.&lt;br /&gt;&lt;br /&gt;The file transfers are incredibly fast, way faster than with WinSCP it seems. They're also handled more gracefully. With WinSCP file transfers are handled in the main window, which is typically covered by my editing popup. So I have to alt-tab to the main window to see whether the transfer is complete. With Coda the file transfer is indicated by a throbber animation on the file listing on the left. When the transfer is complete, a brief notification animation is shown in the top right.&lt;br /&gt;&lt;br /&gt;&lt;img style="float:left; margin:0 10px 10px 0;" src="http://1.bp.blogspot.com/_QCmUgDEhTiw/R5MIhPu_n_I/AAAAAAAAA4A/4uWjPwThvq4/s320/preview-dom%5B1%5D.gif" border="0" /&gt; The editor is decent enough for my needs. It highlights most of the languages you typically encounter during web development and it follows most standard editing conventions. But it could do with some more keyboard shortcuts for faster editing and especially navigation. It also would be nice if it could integrate with external or online help files for supported languages. There's tons of great documentation out there for Java, JavaScript, HTML, CSS, etc. Unfortunately Coda limits its built in help system to the help that comes with the installation. So for anything beyond that, I still need to tab to my browser and search for the help there. This seriously breaks their "one-window web development" mantra and should be addressed if they want to stick to that claim.&lt;br /&gt;&lt;br /&gt;That said: you might notice that the editor has lots of features that the internal WinSCP editor doesn't have, so I shouldn't be complaining. Syntax highlighting is great of course, as are other features like the overview of functions in the current file. The fact that I'm asking for features that you'd normally find in real IDE's and not in WinSCP are actually a big compliment to the Panic people: Coda really feels like a web development IDE!&lt;br /&gt;&lt;br /&gt;The biggest downside of Coda I've found so far is the price tag. Like pretty much all software on the Mac it's commercial. Panic decided to set the price tag to $79. Which definitely doesn't make it an impulse buy for me. So I'll evaluate it a bit longer and see what happens when my trial runs out. I'll probably go back to my XP laptop and WinSCP. But if I then find myself missing Coda features, I'll give in and buy it. After all... that would mean it's better suited to my WinSCP. Who would have ever thought that would be possible?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-8643517708230073646?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/8643517708230073646/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=8643517708230073646' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/8643517708230073646'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/8643517708230073646'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2008/01/winscp-replacement-for-mac.html' title='A WinSCP replacement for the Mac'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_QCmUgDEhTiw/Rqw2c9A8y2I/AAAAAAAAAUQ/5T35rtllifo/s72-c/winscp%5B1%5D.png' height='72' width='72'/><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-2690779792190950352</id><published>2007-12-30T10:45:00.000+01:00</published><updated>2007-12-30T10:52:56.850+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='Cob web'/><category scheme='http://www.blogger.com/atom/ns#' term='mesh'/><category scheme='http://www.blogger.com/atom/ns#' term='grid'/><title type='text'>Grid computing - using web browsers</title><content type='html'>Grid computing is one of those areas that seems to have a magic appeal to software developers. There is something very attractive about taking some relatively simple computers and wielding their combined power to perform seemingly infinitely large computing tasks within reasonable times.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_QCmUgDEhTiw/R3NWxfu_n3I/AAAAAAAAA2o/Qsaw-HuKD7Q/s1600-h/cerngrid.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_QCmUgDEhTiw/R3NWxfu_n3I/AAAAAAAAA2o/Qsaw-HuKD7Q/s320/cerngrid.jpg" alt="" id="BLOGGER_PHOTO_ID_5148554207346794354" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;I've also always been attracted to grids. But as many &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_QCmUgDEhTiw/R3NWa_u_n2I/AAAAAAAAA2g/E4PeFNMJ53A/s1600-h/google-labs%5B1%5D.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://4.bp.blogspot.com/_QCmUgDEhTiw/R3NWa_u_n2I/AAAAAAAAA2g/E4PeFNMJ53A/s200/google-labs%5B1%5D.png" alt="" id="BLOGGER_PHOTO_ID_5148553820799737698" border="0" /&gt;&lt;/a&gt;developers, I too thought this type of power was not within reach for me. Only since Google started documenting the "cloud of commodity PCs" that power their vast computing power, does it suddenly seem quite feasible for even just "large" companies to have their own computing cloud.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;But my problem remains the same. I don't work for Google, Yahoo or IBM and I'm not a large company myself. So I don't have access to a set of commodity PCs that I can combine into a grid. So for years I decided that I'd never get a chance to work on a grid, unless I'd start working for one of those big boys.&lt;br /&gt;&lt;br /&gt;Recently I've been thinking about an alternate setup for a grid computer, more along the lines of the SETI@Home project and all its successors. &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_QCmUgDEhTiw/R3NXdPu_n4I/AAAAAAAAA2w/3cF5dqk9fZQ/s1600-h/seti%40home-boinc-screenshot-02%5B1%5D.gif"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://1.bp.blogspot.com/_QCmUgDEhTiw/R3NXdPu_n4I/AAAAAAAAA2w/3cF5dqk9fZQ/s200/seti%40home-boinc-screenshot-02%5B1%5D.gif" alt="" id="BLOGGER_PHOTO_ID_5148554958966071170" border="0" /&gt;&lt;/a&gt;Those programs all allow home PCs of users all over the world to take part in a giant computer network - a global grid in essence. So the people creating these programs get a lot of computing power, yet they don't have to manage the hardware. A powerful setup.&lt;br /&gt;&lt;br /&gt;But such setups already exist. And they have one downside that keeps them from even more mainstream adoption: they require the user to install software to put their computer into the grid. And although the threshold isn't very high, it's still too high for many people. So a lot of potential computing power is not used, because the barrier of installing software is too high.&lt;br /&gt;&lt;br /&gt;Now that got me thinking: is there an existing platform on modern PCs that we can just embed our own grid applications in? Preferably a platform that's been around for a few years, so all its quirks are known. And it would be nice if the platform comes with built-in internet connectivity.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_QCmUgDEhTiw/R3NX8fu_n5I/AAAAAAAAA24/w16LCkMp4Ow/s1600-h/Bonjour_Browser%5B1%5D.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://2.bp.blogspot.com/_QCmUgDEhTiw/R3NX8fu_n5I/AAAAAAAAA24/w16LCkMp4Ow/s200/Bonjour_Browser%5B1%5D.png" alt="" id="BLOGGER_PHOTO_ID_5148555495836983186" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Here's the idea that popped into my head: web browsers! They used to be nothing more than HTML viewers, but those days are long gone. Nowadays our browsers are hosting more and more complete applications, like GMail, PopFly and Yahoo Pipes. These applications prove that there is a lot of computing power in the web browser. Is it possible to use the web browsers that people have open on their PCs all the time and turn those into nodes in the grid?&lt;br /&gt;&lt;br /&gt;It is a very simple concept: every browser that has a certain URL open is a node in the grid. For a computer to join the grid, they just surf to the URL. To leave the grid again, they navigate away from the URL. It doesn't get much easier than that, right? No software to install, just a page you have to visit. Put it in your favorites in the office, open it every morning when you boot your PC and that's one more node in the grid. From even my own limited reach, I know of at least 5 machines that I could "grid enable" in this way. Those are all PCs and Macs that are on for a large part of the day, just waiting for me or my family to use them. Or that's what they used to be... now I can't stop thinking about them as being nodes in my "web based grid".&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_QCmUgDEhTiw/R3NZv_u_n7I/AAAAAAAAA3I/bf2JX9JkoPc/s1600-h/cobweb%5B1%5D.jpg"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://4.bp.blogspot.com/_QCmUgDEhTiw/R3NZv_u_n7I/AAAAAAAAA3I/bf2JX9JkoPc/s200/cobweb%5B1%5D.jpg" alt="" id="BLOGGER_PHOTO_ID_5148557480111873970" border="0" /&gt;&lt;/a&gt;If you're a software developer reading this, than your mind probably started wandering while reading the last few paragraphs. Is this possible? How would the nodes get their tasks? How would they report their results back? How would you manage the nodes in the grid? Where do you keep the data that is needed for/generated by the nodes? How do you handle XSS issues? Wouldn't the nodes quickly overload the server that manages them? The list of challenges is seemingly endless and definitely too much for me to deal with in one go.&lt;br /&gt;&lt;br /&gt;All I know is that ever since this idea popped into my head, I can't stop thinking about it. And for every problem, I can see at least a few potential solutions. I have no idea whether they'll work or which one is best, but the only way to figure that out is to actually start building the platform.&lt;br /&gt;&lt;br /&gt;Oh man... I really need to make this my 20% project. Or more likely... I really need a lot of people to make this their 20% project. Help?&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_QCmUgDEhTiw/R3NZNvu_n6I/AAAAAAAAA3A/JecRYRf3V8k/s1600-h/help.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_QCmUgDEhTiw/R3NZNvu_n6I/AAAAAAAAA3A/JecRYRf3V8k/s200/help.jpg" alt="" id="BLOGGER_PHOTO_ID_5148556891701354402" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-2690779792190950352?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/2690779792190950352/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=2690779792190950352' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/2690779792190950352'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/2690779792190950352'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2007/12/grid-computing-using-web-browsers.html' title='Grid computing - using web browsers'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_QCmUgDEhTiw/R3NWxfu_n3I/AAAAAAAAA2o/Qsaw-HuKD7Q/s72-c/cerngrid.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-5144492256979879839</id><published>2007-12-22T11:50:00.000+01:00</published><updated>2007-12-22T11:54:45.361+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='web apache httpd fielding REST'/><title type='text'>The origin of the name Apache web server</title><content type='html'>I read a lot. Not much literature and novels as unfortunately those seem to suffer under my more professional reading habits. I read lots of technical articles, white papers, blog posts and specifications. It's part of what I do to keep up to date with the things happening in the CS field. But in part I also read all kinds of stuff to gain a broader understanding of our profession.&lt;br /&gt;&lt;br /&gt;Some of the longer things I read this year include "&lt;a href="http://www.quirksmode.org/book/"&gt;PPK on JavaScript&lt;/a&gt;", "&lt;a href="http://www.amazon.com/Asshole-Rule-Civilized-Workplace-Surviving/dp/0446526568"&gt;The no asshole rule&lt;/a&gt;", but also the venerable "&lt;a href="stephane.ducasse.free.fr/FreeBooks/Art/artAdded174186187Final.pdf"&gt;Art and science of Smalltalk&lt;/a&gt;". And some colleagues even caught me reading an OS9 AppleScript manual dated somewhere around 1999. They're still making fun of their discovery almost every day, but I don't mind... having read that manual has given me a better understanding of how the now much heralded Apple engineers thought about making an end-user programming language almost a decade ago.&lt;br /&gt;&lt;br /&gt;Recently I read the bulk of Roy Thomas Fielding's thesis &lt;a href="http://www.ics.uci.edu/%7Efielding/pubs/dissertation/top.htm"&gt;Architectural Styles and the Design of Network-based Software Architectures&lt;/a&gt; in which he introduces the principles of &lt;a href="http://www.xfront.com/REST-Web-Services.html"&gt;REST&lt;/a&gt;. As with any thesis it is a bit too abstract for my taste, but it did introduce me somewhat better to the background and theory behind REST.&lt;br /&gt;&lt;br /&gt;Aside from that, I made one stunning discover when I read about &lt;a href="http://www.ics.uci.edu/%7Efielding/pubs/dissertation/evaluation.htm#sec_6_4_2"&gt;Fielding's involvement in the creation of the Apache HTTP server&lt;/a&gt;:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;At the time, the most popular HTTP server (httpd) was the public domain software developed by Rob McCool at the National Center for Supercomputing Applications, University of Illinois, Urbana-Champaign (NCSA). However, development had stalled after Rob left NCSA in mid-1994, and many webmasters had developed their own extensions and bug fixes that were in need of a common distribution. A group of us created a mailing list for the purpose of coordinating our changes as "patches" to the original source. In the process, we created the Apache HTTP Server Project&lt;/li&gt;&lt;/ul&gt;Please read that last part again, and again... and again. Until it hits you where it finally hit me. What hit me? Well... I finally understood that the name of the Apache web server might (originally) have had nothing to do with the Apache tribe. The server was created by taking an existing code base and then applying all sort of patches. So in a sense it was a patchy web server. A patchy... Apache...!&lt;br /&gt;&lt;br /&gt;Brilliant! In all my years of knowing the Apache web server and the brand that was created around the Apache name, I never realized where it came from.&lt;br /&gt;&lt;br /&gt;The &lt;a href="http://apache.org/foundation/faq.html#name"&gt;Apache website&lt;/a&gt; itself has this to say about it:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The name 'Apache' was chosen from respect for the Native American Indian tribe of Apache, well-known for their superior skills in warfare strategy and their inexhaustible endurance. It also makes a cute pun on "a patchy web server" -- a server made from a series of patches -- but this was not its origin.&lt;/li&gt;&lt;/ul&gt;For the moment I'll take their word for it and accept that the name sounding like "a patchy web server" is pure coincidence. I bet it's also more convenient for them in selling the Apache brand: "we named our web server after its inexhaustible endurance" sounds a lot better than "we named our web server after the fact that it was created from a bunch of unrelated patches".&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-5144492256979879839?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/5144492256979879839/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=5144492256979879839' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/5144492256979879839'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/5144492256979879839'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2007/12/origin-of-name-apache-web-server.html' title='The origin of the name Apache web server'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-8653126802468439582</id><published>2007-11-17T16:02:00.000+01:00</published><updated>2007-11-17T16:08:21.270+01:00</updated><title type='text'>Why devx needs a better print function</title><content type='html'>I like to read technology articles during my daily commute. And since the train is too crowded for a laptop and I don't have an ebook reader (yet), I still print articles that seem interesting to read during the train ride.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://i.dell.com/images/global/products/314x314/printer_1720.jpg"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 320px;" src="http://i.dell.com/images/global/products/314x314/printer_1720.jpg" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;A lot of web sites still have a Print button. What happens when you click that button differs from site to site, but it roughly falls into these categories:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Show all pages at once&lt;br /&gt;Many sites break articles into multiple pages. The print version of the article puts all of these pages together again, to allow them to be printed in one go.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Re-layout the site to print better&lt;br /&gt;Tables seem to be notoriously difficult to print. That's why many sites revert to a table-less layout in their print version&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Remove navigation elements&lt;br /&gt;Global and local navigation elements are pretty useless on paper. So they're removed from the print layout.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Images - click to see full size version&lt;br /&gt;Some graphics-intensive sites show images of reduced size in their normal view, showing the full version in a popup when you click some link. Since you can't click a link in the Print version, the full size images should always be shown there.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;These are some things that I wish more site would do:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Replace animated adds by text adds&lt;br /&gt;I don't mind showing adds next to good content. I do mind the ignorance of including animated adds in a print layout. I'm pretty sure no printer will deal with these in a useful way.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Use images that are more appropriate for B&amp;amp;W&lt;br /&gt;Most people still use B&amp;amp;W printers. So it would be nice if sites allowed the option of replacing their colored images with version that are more suited to printing on a B&amp;amp;W printer.&lt;br /&gt;A common example of this are mostly-black screenshots like from command prompts/shell windows. When printed these really eat through a toner at high speed. It would be nice if a site would allow me to replace those images with ones that are mostly white, making my toner last longer.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;That's a pretty long list. And most of these things can actually be accomplished on a website without needing a special print version of the articles. Hiding navigation elements, showing non-animated adds and other layout tricks on a print version can easily be accomplished using CSS media types. And why do most sites still use tables for their layouts? Just remove those tables and you have one less difference between the screen and the print version. And I also think it would make sense to show all content on a single page.&lt;br /&gt;&lt;br /&gt;So that actually leaves just one reason for having a Print button: showing full sized images inline. And that finally brings us to the title of the article: the print function of &lt;a href="http://www.devx.com/"&gt;DevX&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://assets.devx.com/devx/7819.gif"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px;" src="http://assets.devx.com/devx/7819.gif" alt="" border="0" /&gt;&lt;/a&gt;DevX is a nice development site that sometimes has very interesting content. And one of the reasons their content is good is that they usually include quite a lot of screenshots and diagrams. This just makes their articles so much easier to follow. On screen the articles show the images at a reduced size. Which makes sense, because the images are often full screen screenshots which would otherwise leave hardly any room for text.&lt;br /&gt;&lt;br /&gt;But if you've ever printed an article from www.devx.com you've probably noticed their print versions still only show the images with a reduced size. They're not replaced by the full-resolution version. They're not printed in a larger box. They're not even added at the end of the article, like appendices. The images in the print version are exactly the same as in the screen version: reduced to sometimes a tenth of the the original size.&lt;br /&gt;&lt;br /&gt;So whenever I find an article in DevX that I want to read on the train, I start up Word and open the print version in there. Then I remove all tables, because they also don't print very well from Word. Then I go back to the browser and open each image, copy it to the clipboard, paste it in Word and then remove the useless downsized version.&lt;br /&gt;&lt;br /&gt;And although I normally like the high volume of screenshots that DevX uses in their articles, this is actually a reason why I'd like them to use less screenshots and more text. Because this conversion to Word is not just a lot of mindless work; I sometimes forget to do it and print a DevX article as is. And by the time I realize what I've done, I'm already on the train. So I do my best and squint my eyes trying to read the text in there.&lt;br /&gt;&lt;br /&gt;So there you have it: please DevX fix your @$@%&amp;amp;^# Print function.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-8653126802468439582?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/8653126802468439582/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=8653126802468439582' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/8653126802468439582'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/8653126802468439582'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2007/11/why-devx-needs-better-print-function.html' title='Why devx needs a better print function'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-2975576393895069469</id><published>2007-10-06T08:20:00.000+01:00</published><updated>2007-10-06T07:22:16.666+01:00</updated><title type='text'>Viewing and editing Scrum project management data with Google Mashup Editor</title><content type='html'>Welcome to my first post on the Google Mashup Editor. In this article we'll create a tool for entering and storing data using Google's new mashup editor tool. Depending on available time, the evolution of Google Mashup Editor and the availability of alternative tools, I might improve on the basic data management application in later articles.&lt;br /&gt;&lt;h2&gt;Scrum project management&lt;br /&gt;&lt;/h2&gt;The application we'll be creating is a Scrum project management tool. If you don't know Scrum yet, it's an agile project management framework. Please do yourself a huge favor and read &lt;a href="http://blog.crisp.se/henrikkniberg/2006/11/26/1164574680000.html" title="Scrum and XP from the Trenches"&gt;this 90 page story about Scrum&lt;/a&gt; (&lt;a href="http://www.crisp.se/henrik.kniberg/ScrumAndXpFromTheTrenches.pdf" title="Scrum and XP from the Trenches"&gt;pdf&lt;/a&gt;). It's a good and fun read and has already won over many organizations to at least give Scrum a try.&lt;br /&gt;&lt;br /&gt;My reasons for wanting to create this type of application are many. One of them is that there seems to be no tool that satisfies my needs with the right price tag. XPlanner is good, but very basic. Mingle looks nice, but is too expensive and a real resource hog. ExtremePlanner also looks nice, but again: it seems a bit expensive for my taste. But one other reason is probably more important than the price issue: building this data model seems do-able and gives me a chance to get to know Google Mashup Editor a bit more.&lt;br /&gt;&lt;h2&gt;Google Mashup Editor&lt;br /&gt;&lt;/h2&gt;Mashup tools seem to be a dime a dozen these days. These tools try to take programming to the masses, allowing everyone to create complex web applications based on existing data or logic.&lt;br /&gt;&lt;br /&gt;Yahoo was the first big player in this field, with their &lt;a href="http://pipes.yahoo.com/"&gt;Yahoo Pipes&lt;/a&gt;. They're aiming for a &lt;a href="http://en.wikipedia.org/wiki/Visual_programming_language"&gt;visual programming environment&lt;/a&gt; where the user manipulates blocks rather than writing code. Microsoft followed suit with &lt;a href="http://www.popfly.ms/"&gt;Popfly&lt;/a&gt;, an even richer mashup creation environment combined with what seems to be the next generation of their MSN Spaces platform.&lt;br /&gt;&lt;br /&gt;Google was the last entrant into this field (if I recall correctly) and the first glances at their entry into the field left me rather disappointed. No drag-and-drop programming, no cool default widgets, just a pretty basic text editor and some basic tags.&lt;br /&gt;&lt;br /&gt;But if you look below the surface you can see that Google Mashup Editor (GME) is actually quite different from the other two. Where Yahoo and Microsoft just seem to focus on allowing you to read and combine data from various sources, Google also allows you to create new applications from scratch. In that respect GME is more of an application creation (and hosting) platform than a mashup editor.&lt;br /&gt;&lt;br /&gt;Much of these additional possibilities seem so originate from Google's adoption of the &lt;a href="http://atompub.org/rfc4287.html"&gt;Atom Publishing Protocol&lt;/a&gt;, exposed through the &lt;a href="http://code.google.com/apis/gdata/index.html"&gt;Google Data (GData) APIs&lt;/a&gt;. This API is what makes GME not only a mashup editor, but also a valid tool for creating completely standalone applications. These applications are then hosted on Google's servers, using Google's servers for data storage, using the GME to create and update the applications. Some people might not like to put so much in the hands of Google. But it will certainly lower the bar for creating scalable web 2.0 applications.&lt;br /&gt;&lt;br /&gt;That's enough of the background talk. Let's get to work on the application.&lt;br /&gt;&lt;h2&gt;Initial data model&lt;br /&gt;&lt;/h2&gt; We'll start by defining the basic entities and relations in our application. We'll probably expand on these later, but we can get pretty far with just the following.&lt;br /&gt;&lt;br /&gt;A project is something on which a team works in sprints to create a product or a release of a product. This is all intentionally very vague, as our application doesn't need to know the details of the projects it manages.&lt;br /&gt;&lt;br /&gt;A project has a product owner and a scrum master. Aside from that there are other team members, but we'll leave them out of the equation for now.&lt;br /&gt;&lt;br /&gt;A sprint is a time period during which the team implements certain stories. A sprint has a start date and end date and a description of the general goal of the sprint.&lt;br /&gt;&lt;br /&gt;A story is a piece of functionality that the team creates. It has a name, a description of how to demonstrate it and an estimate of the effort it will take to create the functionality. Stories can be either user-focused or technical in nature.&lt;br /&gt;&lt;br /&gt;All stories combined are called the product backlog. Stories from the product backlog are planned into sprints. So each project has one product backlog and some of the stories in this product backlog are planned into each sprint.&lt;br /&gt;&lt;br /&gt;This all translates into the following very simple data model:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_QCmUgDEhTiw/RtEs5y759uI/AAAAAAAAAbA/XjEimDBPeYI/s1600-h/scrummer_EAR.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_QCmUgDEhTiw/RtEs5y759uI/AAAAAAAAAbA/XjEimDBPeYI/s400/scrummer_EAR.png" alt="" id="BLOGGER_PHOTO_ID_5102909224224683746" border="0" /&gt;&lt;/a&gt;Let's see how we can translate this data model into GME.&lt;br /&gt;&lt;h2&gt;Creating the project list in GME&lt;/h2&gt;The first step is to create a new project in GME. This will show you a nice empty application with just a pair of &lt;tt&gt;gm:page&lt;/tt&gt; tags.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;gm:page title="Scrum Project Manager" authenticate="true"&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/gm:page&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Everything for our application will be inside the gm:page tags. If you want your application to have multiple pages, just add some more files to it. But for this application a single page will do.&lt;br /&gt;&lt;br /&gt;Getting data into GME consists of two steps: defining the data itself and defining the GUI for it. The data itself takes the form of a &lt;tt style="color: rgb(0, 0, 153);"&gt;gm:list&lt;/tt&gt; tag:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;gm:list id="Projects" data="${app}/Projects" template="projectList" /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The &lt;span style="color: rgb(0, 0, 153);font-family:courier new;" &gt;gm:list&lt;/span&gt; tag defines a list of data that is used in the application. In many applications the data will be pulled from an external -RSS or Atom- feed. But we want to store the data inside the application, right in Google's mashup servers.&lt;br /&gt;&lt;br /&gt;The data of our project list is stored under the &lt;tt style="color: rgb(0, 153, 0);"&gt;${app}&lt;/tt&gt;. This is a location (a "feed" in GME terms) where the data of all users of the application is stored. If we don't want to share the data between users, we can store it under &lt;tt style="color: rgb(0, 153, 0);"&gt;${user}&lt;/tt&gt;, which is data that is kept per user. Currently there is no way to have data shared between some users (but not all users of the application), although this feature will probably be added in the future.&lt;br /&gt;&lt;br /&gt;To display the data in the list, the page needs a template. A template determines what fields to display and how to display them. It's easiest to use an HTML table, so we'll do that for now.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;gm:template id="projectList"&gt;&lt;br /&gt;&amp;lt;table class="gm-table"&gt;&lt;br /&gt;   &amp;lt;thead&gt;&amp;lt;tr&gt;&lt;br /&gt;      &amp;lt;td width="200"&gt;Name&amp;lt;/td&gt;&lt;br /&gt;      &amp;lt;td width="100"&gt;Product owner&amp;lt;/td&gt;&lt;br /&gt;      &amp;lt;td width="100"&gt;Scrum master&amp;lt;/td&gt;&lt;br /&gt;      &amp;lt;td width="45"&gt; &amp;lt;/td&gt;&lt;br /&gt;   &amp;lt;/tr&gt;&amp;lt;/thead&gt;&lt;br /&gt;   &amp;lt;tr repeat="true"&gt;&lt;br /&gt;      &amp;lt;td&gt;&amp;lt;gm:text ref="atom:title" hint="Project name"/&gt;&amp;lt;/td&gt;&lt;br /&gt;      &amp;lt;td&gt;&amp;lt;gm:text ref="gmd:productOwner"/&gt;&amp;lt;/td&gt;&lt;br /&gt;      &amp;lt;td&gt;&amp;lt;gm:text ref="gmd:scrumMaster"/&gt;&amp;lt;/td&gt;&lt;br /&gt;      &amp;lt;td&gt;&amp;lt;gm:editButtons/&gt;&amp;lt;/td&gt;&lt;br /&gt;   &amp;lt;/tr&gt;&lt;br /&gt;   &amp;lt;tfoot&gt;&amp;lt;tr&gt;&lt;br /&gt;      &amp;lt;td colspan="4" align="right"&gt;&amp;lt;gm:create label="New project"/&gt;&amp;lt;/td&gt;&lt;br /&gt;   &amp;lt;/tr&gt;&amp;lt;/tfoot&gt;&lt;br /&gt;&amp;lt;/table&gt;&lt;br /&gt;&amp;lt;/gm:template&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As you can see we're mixing standard HTML tags, with GME specific tags like &lt;tt style="color: rgb(0, 0, 153);"&gt;gm:text&lt;/tt&gt;, &lt;tt style="color: rgb(0, 0, 153);"&gt;gm:editButtons&lt;/tt&gt; and &lt;tt style="color: rgb(0, 0, 153);"&gt;gm:create&lt;/tt&gt;. Also notice the non-HTML &lt;tt style="color: rgb(0, 0, 153);"&gt;repeat&lt;/tt&gt; attribute on the second &lt;tt style="color: rgb(0, 0, 153);"&gt;tr&lt;/tt&gt; (a normal HTML table row). This tells GME to repeat that &lt;tt style="color: rgb(0, 0, 153);"&gt;tr&lt;/tt&gt; for every item in the &lt;tt style="color: rgb(0, 153, 0);"&gt;${app}/Projects&lt;/tt&gt; feed.&lt;br /&gt;&lt;br /&gt;If we now compile and test this application, we get an empty table with a "New project" button. Pressing the button adds an empty row to the table, with fields to fill in the values for a product.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_QCmUgDEhTiw/RtEyjS759xI/AAAAAAAAAbY/Ra0rcGYW4gM/s1600-h/gme_new_project.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_QCmUgDEhTiw/RtEyjS759xI/AAAAAAAAAbY/Ra0rcGYW4gM/s400/gme_new_project.png" alt="" id="BLOGGER_PHOTO_ID_5102915434747393810" border="0" /&gt;&lt;/a&gt;Note that editing and creation functionality are for free with GME. Although they're not very flexible, they allow you to quickly get started.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Creating the list of stories in GME&lt;/h2&gt;Next is a list of stories for a project. Since stories are always part of a project, we store the data under the feed of a project.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;h2&gt;Stories for selected project&amp;lt;/h2&gt;&lt;br /&gt;&amp;lt;gm:list id="Stories" data="${Projects}/Stories" template="storyList" /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is where GME really adds a lot of logic automatically. The  location refers to a child of the &lt;span style="color: rgb(0, 153, 0);font-family:courier new;" &gt;${Projects}/StoriesProjects&lt;/span&gt;       list we defined earlier. Each project in the &lt;span style="color: rgb(0, 153, 0);font-family:courier new;" &gt;Projects&lt;/span&gt; list will have its own list of &lt;span style="color: rgb(0, 153, 0);font-family:courier new;" &gt;Stories&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;This list also needs a template to display it, which is really similar to the one for the projects.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;gm:template id="storyList"&gt;&lt;br /&gt;&amp;lt;table class="gm-table"&gt;`&lt;br /&gt;   &amp;lt;thead&gt;&amp;lt;tr&gt;&lt;br /&gt;      &amp;lt;td width="200"&gt;Title&amp;lt;/td&gt;&lt;br /&gt;      &amp;lt;td width="75"&gt;Type&amp;lt;/td&gt;&lt;br /&gt;      &amp;lt;td width="25"&gt;Estimate&amp;lt;/td&gt;&lt;br /&gt;      &amp;lt;td width="100"&gt;How to demo&amp;lt;/td&gt;&lt;br /&gt;      &amp;lt;td width="45"&gt;&amp;lt;/td&gt;&lt;br /&gt;   &amp;lt;/tr&gt;&amp;lt;/thead&gt;&lt;br /&gt;   &amp;lt;tr repeat="true"&gt;&lt;br /&gt;      &amp;lt;td&gt;&amp;lt;gm:text ref="atom:title" hint="Story title"/&gt;&amp;lt;/td&gt;&lt;br /&gt;      &amp;lt;td&gt;&lt;br /&gt;         &amp;lt;gm:select ref="gmd:storyType"&gt;&lt;br /&gt;            &amp;lt;gm:option value="user" selected="true"&gt;User&amp;lt;/gm:option&gt;&lt;br /&gt;            &amp;lt;gm:option value="tech"&gt;Tech&amp;lt;/gm:option&gt;&lt;br /&gt;         &amp;lt;/gm:select&gt;&lt;br /&gt;      &amp;lt;/td&gt;&lt;br /&gt;      &amp;lt;td&gt;&amp;lt;gm:number ref="gmd:estimate"/&gt;&amp;lt;/td&gt;&lt;br /&gt;      &amp;lt;td&gt;&amp;lt;gm:text ref="gmd:howToDemo"/&gt;&amp;lt;/td&gt;&lt;br /&gt;      &amp;lt;td&gt;&amp;lt;gm:editButtons/&gt;&amp;lt;/td&gt;&lt;br /&gt;   &amp;lt;/tr&gt;&lt;br /&gt;   &amp;lt;tfoot&gt;&amp;lt;tr&gt;&lt;br /&gt;      &amp;lt;td colspan="5" align="right"&gt;&amp;lt;gm:create label="New story"&gt;&amp;lt;/td&gt;&lt;br /&gt;   &amp;lt;/tr&gt;&amp;lt;/tfoot&gt;&lt;br /&gt;&amp;lt;/table&gt;&lt;br /&gt;&amp;lt;/gm:template&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now the only tricky bit we still need to do for the list of stories, is that it needs to be refreshed when the user selects a different project. This is quite easy, by setting an event handler.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;h2&gt;Unplanned stories for selected project&amp;lt;/h2&gt;&lt;br /&gt;&amp;lt;gm:list id="ProjectStories" data="${Projects}/Stories" template="storyList"&gt;&lt;br /&gt;&amp;lt;gm:handleEvent src="Projects"/&gt;&lt;br /&gt;&amp;lt;/gm:list&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This tells the story list to refresh itself when an event happens in the Projects list we defined before. So select a different project will display the stories for that project.&lt;br /&gt;&lt;br /&gt;So after adding the story list and adding some projects and stories, our application looks like this:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_QCmUgDEhTiw/Rwck9ryPUHI/AAAAAAAAAwc/72Ye-_InkHI/s1600-h/Scrummer2.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_QCmUgDEhTiw/Rwck9ryPUHI/AAAAAAAAAwc/72Ye-_InkHI/s400/Scrummer2.png" alt="" id="BLOGGER_PHOTO_ID_5118100143673921650" border="0" /&gt;&lt;/a&gt;We can easily do the same for the list of sprints for the project. Since this is really similar to the list of stories, I won't show the code here. If you want to have a look at the code, look at the finished project on &lt;span class="gme-published"&gt;&lt;/span&gt;&lt;a class="gm-nolink gme-published" target="HOSTING" href="http://scrummer.googlemashups.com/"&gt;http://scrummer.googlemashups.com&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Last is the list of stories for the selected sprint. Note that stories can either be part of the project or part of the sprint. So for now we'll call the first type "unplanned stories". Later we'll want to share the stories between the project and the sprints.&lt;br /&gt;&lt;br /&gt;Since the list of stories is -again- really similar to the list of unplanned stories, we won't show the code here. But when we now run our mashup it looks like this:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_QCmUgDEhTiw/Rwcn0LyPUII/AAAAAAAAAwk/Ca379oRrjio/s1600-h/Scrummer4.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_QCmUgDEhTiw/Rwcn0LyPUII/AAAAAAAAAwk/Ca379oRrjio/s400/Scrummer4.png" alt="" id="BLOGGER_PHOTO_ID_5118103279000047746" border="0" /&gt;&lt;/a&gt;At the bottom you can see that I am entering a story. This is almost a usable application, at least for entering and browsing the data. To make it something you'd really want your entire team to use for your daily managing of Scrum projects, it would require more work.&lt;br /&gt;&lt;br /&gt;That's it for now. If you want to have a look at the finished code or play with the application, go to &lt;span class="gme-published"&gt;&lt;/span&gt;&lt;a class="gm-nolink gme-published" target="HOSTING" href="http://scrummer.googlemashups.com/"&gt;http://scrummer.googlemashups.com&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-2975576393895069469?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/2975576393895069469/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=2975576393895069469' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/2975576393895069469'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/2975576393895069469'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2007/10/viewing-and-editing-scrum-project.html' title='Viewing and editing Scrum project management data with Google Mashup Editor'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_QCmUgDEhTiw/RtEs5y759uI/AAAAAAAAAbA/XjEimDBPeYI/s72-c/scrummer_EAR.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-9133121269543887953</id><published>2007-08-25T10:45:00.001+01:00</published><updated>2010-01-01T00:37:27.150+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Burndown'/><category scheme='http://www.blogger.com/atom/ns#' term='Sprint'/><category scheme='http://www.blogger.com/atom/ns#' term='Scrum'/><category scheme='http://www.blogger.com/atom/ns#' term='Certified ScrumMaster'/><category scheme='http://www.blogger.com/atom/ns#' term='burndown chart'/><title type='text'>Online burndown chart generator</title><content type='html'>One of the aspects of Scrum is its focus on transparency - getting all information out in the open. And one of the areas that enables the transparency is the burndown chart. It's a public posting of the progress of the team throughout its current sprint.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_QCmUgDEhTiw/Rs_oGS759sI/AAAAAAAAAaw/gzCsZJhbDrM/s1600-h/DSC00253.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_QCmUgDEhTiw/Rs_oGS759sI/AAAAAAAAAaw/gzCsZJhbDrM/s320/DSC00253.JPG" alt="" id="BLOGGER_PHOTO_ID_5102552097694021314" border="0" /&gt;&lt;/a&gt;On the horizontal axis you see the days of this sprint. The vertical axis describes the amount of work. At the top of the vertical axis is the number of "ideal man hours" we committed to for this sprint. The straight diagonal line is the "ideal burndown" that we're aiming for. The slightly less straight line is our actual burndown. As you can see this chart is from somewhere during the third week of our four-week sprint and we're slightly above target. But things don't look as desperate as a few days before, thanks to some colleagues getting back from Holidays (which it says in the small scribling that you probably can't read).&lt;br /&gt;&lt;br /&gt;As a Scrum master I like to post this information as publicly as I can. So just having it on the wall of our team room isn't good enough, since there are many people that don't visit our team room. Ideally I'd like to have the burndown chart projected on a wall in the central hallway of our office, so everyone can see it first thing they come in in the morning. But as a nice step along the way to this, I chose to publish the chart (and the rest of our product backlog) on our project wiki.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_QCmUgDEhTiw/Rs_qFy759tI/AAAAAAAAAa4/3u8AvM9I2Sc/s1600-h/DSC00279.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_QCmUgDEhTiw/Rs_qFy759tI/AAAAAAAAAa4/3u8AvM9I2Sc/s320/DSC00279.JPG" alt="" id="BLOGGER_PHOTO_ID_5102554288127342290" border="0" /&gt;&lt;/a&gt;In the first sprints I did this by taking a photograph of the burndown chart every morning, right after updating it. I'd then upload the photo to our wiki. The only problem is... uploading them every day turned out to be too much of a hassle. So the wiki actually only got updated once a week. And that's not good for transparency of course.&lt;br /&gt;&lt;br /&gt;So this time around we went searching for a simple tool that would lower the threshold of updating the burndown chart on our wiki. We searched for an extension to MediaWiki that allows you to create a chart by just entering the numbers in your wiki text. That turned out to be quite a challenge. There are many charting and drawing extensions for MediaWiki, but they either didn't do what I wanted or we couldn't get them to work on our wiki.&lt;br /&gt;&lt;br /&gt;In the end I just gave up and wrote a simple web page that -when fed with the right parameters- will return a PNG image of the burndown chart. You call the page like this:&lt;br /&gt;&lt;ul style="list-style-type: none;"&gt;&lt;li&gt;&lt;tt&gt;burndown.jsp?days=1,2,3,6,7,8,9,10,13,14&amp;work=200,170,165,150,125,95&lt;br /&gt;&lt;/tt&gt;&lt;/li&gt;&lt;/ul&gt;And the page will return the following image:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_QCmUgDEhTiw/Rs_mSy759qI/AAAAAAAAAaI/KNx0tk2wmBw/s1600-h/burndown2.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_QCmUgDEhTiw/Rs_mSy759qI/AAAAAAAAAaI/KNx0tk2wmBw/s400/burndown2.png" alt="" id="BLOGGER_PHOTO_ID_5102550113419130530" border="0" /&gt;&lt;/a&gt;So the &lt;tt&gt;days&lt;/tt&gt; parameter indicates the day numbers shown on the bottom. I entered all of them for the entire sprint right away. The &lt;tt&gt;work&lt;/tt&gt; parameter is the work remaining. I just entered the values that I know, which is why the green line stops halfway through.&lt;br /&gt;&lt;br /&gt;The generated chart is really simple and not very pretty. But it is very easy to keep up to date and that's what counts most. I just add the remaining hours at the end of the URL every morning... and that's it.&lt;br /&gt;&lt;br /&gt;Although I consider this generator a stop gap solution until I find something better, I imagine it might also be useful to other budding Scrum masters. For that reason I've put the page online for public use at &lt;a href="http://apps.vanpuffelen.net/charts/burndown.jsp"&gt;http://apps.vanpuffelen.net/charts/burndown.jsp&lt;/a&gt;. Just click the link and you'll get some usage examples.&lt;br /&gt;&lt;br /&gt;Let me know if this generator is useful to you in the comments section. Also let me know if there's something wrong with it and I'll do my best to fix it.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update&lt;/b&gt; (January 1st, 2010): in my company we've created a custom version of this same tool and used that in many projects over the last few years. This public burndown generator has drawn over 60.000 charts in 2009 alone, so apparently we're not the only ones who use burndown charts. That's why I've now updated the tool with the best features that we've added over time at my company. Check the latest version on &lt;a href="http://apps.vanpuffelen.net/charts/burndown.jsp"&gt;http://apps.vanpuffelen.net/charts/burndown.jsp&lt;/a&gt; for all the features and let me know what you think of them.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-9133121269543887953?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/9133121269543887953/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=9133121269543887953' title='12 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/9133121269543887953'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/9133121269543887953'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2007/08/online-burndown-chart-generator.html' title='Online burndown chart generator'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_QCmUgDEhTiw/Rs_oGS759sI/AAAAAAAAAaw/gzCsZJhbDrM/s72-c/DSC00253.JPG' height='72' width='72'/><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-5529612562479362576</id><published>2007-08-19T09:30:00.002+01:00</published><updated>2010-06-26T15:29:38.649+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='utilization'/><category scheme='http://www.blogger.com/atom/ns#' term='velocity'/><category scheme='http://www.blogger.com/atom/ns#' term='project management'/><category scheme='http://www.blogger.com/atom/ns#' term='Scrum'/><title type='text'>Scrum: utilization vs. velocity</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_QCmUgDEhTiw/RsfwZy759mI/AAAAAAAAAZQ/0x-MOljtb-g/s1600-h/scrum_cufflinks.jpg"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://2.bp.blogspot.com/_QCmUgDEhTiw/RsfwZy759mI/AAAAAAAAAZQ/0x-MOljtb-g/s200/scrum_cufflinks.jpg" alt="" id="BLOGGER_PHOTO_ID_5100309428980807266" border="0" /&gt;&lt;/a&gt;At work we're recently started using Scrum for running some projects. As expected we need to slowly learn the lessons. One of the things we're been having a lot of discussion on recently is the meaning of the focus factor. Let me begin by explaining what a focus factor is, at least in my company.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_QCmUgDEhTiw/RsfvhC759iI/AAAAAAAAAYw/7jYf0PcEzkA/s1600-h/estimate.jpg"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://3.bp.blogspot.com/_QCmUgDEhTiw/RsfvhC759iI/AAAAAAAAAYw/7jYf0PcEzkA/s200/estimate.jpg" alt="" id="BLOGGER_PHOTO_ID_5100308454023231010" border="0" /&gt;&lt;/a&gt;To determine how much work you can do in a sprint, you need to estimate the top stories. We estimate these stories in "ideal man days" using planning poker. This means that each developer answers the question: if we lock you into a room each day without any distractions, after how many days would you have this story finished?&lt;br /&gt;&lt;br /&gt;After these estimates we determine people's availability for the project. After all, they might also be assigned to other projects, if only for part of their time. Even people that have no other projects, tend to have other activities. Like answering questions from customer support or consultants, department meetings, company wide meetings, job interviews with candidates or just playing a game of fusball, table tennis or bowling on the Wii. So basically nobody is available to a project 100% of the time. At most it's 80% - 90% and on overage it seems to be about 60% - 70%.&lt;br /&gt;&lt;br /&gt;So the first stab at determining how much work someone can complete is:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;available hours = contract hours * availability&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;But when you're working on the project, you're not going to always be contributing towards the goals that you've picked up. Within Scrum there is the daily Scrum meeting. It lasts no more &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_QCmUgDEhTiw/RsfwpC759nI/AAAAAAAAAZY/s40oPOgvypI/s1600-h/agile_guy.jpg"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://3.bp.blogspot.com/_QCmUgDEhTiw/RsfwpC759nI/AAAAAAAAAZY/s40oPOgvypI/s200/agile_guy.jpg" alt="" id="BLOGGER_PHOTO_ID_5100309690973812338" border="0" /&gt;&lt;/a&gt;than 15 minutes, but those are minutes that nobody in the team is working towards the goal. And after the meeting a few team members always stick around to discuss some problem further. Such time is very well spent, but it probably wasn't included in the original estimate. So it doesn't bring the "remaining hours" down very much. I see all this meeting, discussion, coaching and tutoring as necessary work. But work that doesn't bring the team much closer to the goal of the sprint. I used to call this overhead, but that sounded like we were generating waste. So in lieu of the agile world I switched to using the term focus factor. So now we have:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;velocity = contract hours * availability * focus factor&lt;/li&gt;&lt;/ul&gt;So the speed at which we get things done (velocity) is the time we're working minus the time we loose to non-project work minus the time we loose on work that doesn't immediately get us closer to the goal. In the past I probably would have included a few more factors in there, but in an agile world this is already accurate enough to get a decent indication of how long it will take us to get something done.&lt;br /&gt;&lt;br /&gt;If there's one thing I've learned from the agile movement and Scrum it's to focus on "when will it be done" instead of "how much time will it take". So to focus on velocity instead of utilization.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_QCmUgDEhTiw/Rsfv1S759jI/AAAAAAAAAY4/edy1mKRaMgw/s1600-h/utilization.png"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://4.bp.blogspot.com/_QCmUgDEhTiw/Rsfv1S759jI/AAAAAAAAAY4/edy1mKRaMgw/s200/utilization.png" alt="" id="BLOGGER_PHOTO_ID_5100308801915582002" border="0" /&gt;&lt;/a&gt;Utilization is the territory of classic project management. It's trying to make sure that every hour of every employee is fully accounted for. So if they're programming, they should have a time-writing slot for programming; if they're meeting, there's a slot for meeting; if they're reviewing designs, there's a slot for that and if they're drinking coffee or playing the Wii... you get the picture. Of course there's no project manager that wants all that level of detail. But in general they are focused on what you're spending your time on.&lt;br /&gt;&lt;br /&gt;Agile thinkers see this really differently. They say: it doesn't really matter how much time you spend, what matters is when it is done. This sounds contradictory so let's see if a small example can make it clearer what I'm trying to say.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_QCmUgDEhTiw/RsfxSi759oI/AAAAAAAAAZg/9wjvPfdFjPQ/s1600-h/deadline.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_QCmUgDEhTiw/RsfxSi759oI/AAAAAAAAAZg/9wjvPfdFjPQ/s200/deadline.jpg" alt="" id="BLOGGER_PHOTO_ID_5100310403938383490" border="0" /&gt;&lt;/a&gt;If I tell my boss that some feature he wants will be done at the end of next week, he is interested in only one thing: that it is done next week. If we get it done on time, he doesn't care whether I spent two hours per day on it or whether it was twelve hours per day. I care about it of course, because I don't want to work late every night. And there's also a limit to the amount of gaming I like to do during a day, so two hours per day will leave me bored quickly. But to my boss, all that matters is when I deliver, not how much effort it took.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_QCmUgDEhTiw/RsfwBi759kI/AAAAAAAAAZA/46QVRU7xdsA/s1600-h/velocity_speedboat.jpg"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://1.bp.blogspot.com/_QCmUgDEhTiw/RsfwBi759kI/AAAAAAAAAZA/46QVRU7xdsA/s200/velocity_speedboat.jpg" alt="" id="BLOGGER_PHOTO_ID_5100309012368979522" border="0" /&gt;&lt;/a&gt;This is why the focus for Scrum projects is on velocity and not on utilization. So in Scrum you want to know how many hours you still need to spend on a job, not how many you've already spent on it. A classic project manager might be really proud that you worked late all week and clocked in 50+ hours. An agile project manager will note that you reduced the "hours remaining" by 10 hours and nothing more. If you're looking for compliments on all your hard work, then Scrum might not be for you.&lt;br /&gt;&lt;br /&gt;Learn more:&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://frank.vanpuffelen.net/2008/02/scrum-story-points-ideal-man-days-real.html"&gt;Scrum: story points, ideal man days, real man week&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;a simple tool to generate &lt;a href="http://frank.vanpuffelen.net/2007/08/online-burndown-chart-generator.html"&gt;burndown charts online&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://frank.vanpuffelen.net/2010/03/who-estimates-stories-in-scrum.html"&gt;Who estimates the stories in Scrum?&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-5529612562479362576?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/5529612562479362576/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=5529612562479362576' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/5529612562479362576'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/5529612562479362576'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2007/08/scrum-utilization-vs-velocity.html' title='Scrum: utilization vs. velocity'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_QCmUgDEhTiw/RsfwZy759mI/AAAAAAAAAZQ/0x-MOljtb-g/s72-c/scrum_cufflinks.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-6723519643860230494</id><published>2007-08-11T18:19:00.000+01:00</published><updated>2007-08-11T17:20:01.085+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='xda'/><category scheme='http://www.blogger.com/atom/ns#' term='dlan'/><category scheme='http://www.blogger.com/atom/ns#' term='wlan'/><category scheme='http://www.blogger.com/atom/ns#' term='wifi'/><category scheme='http://www.blogger.com/atom/ns#' term='iphone'/><category scheme='http://www.blogger.com/atom/ns#' term='devolo'/><category scheme='http://www.blogger.com/atom/ns#' term='networking'/><title type='text'>Will wireless work?</title><content type='html'>Friends often call me a geek. They mean no offense, so I try to take none. As Chris Pirillo once put it: "geek used to be a four letter word, now it's a six figure one". Well... that last part isn't exactly true for me, but that's probably only so because I get paid in euros instead of dollars.&lt;br /&gt;&lt;br /&gt;Part of what makes me a geek is the fact that I tend to be an early adopter of new technologies. I got my first always-on internet connection in 1996 paying somewhere around 40 euros for a speed that never seemed to top 1.5 kbps. Yes, that's kpbs for kilobits per second - so about 40 times slower than an analog modem. But it was always on... so I was one of those people that knew they had email a few seconds after the other party had sent it.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_QCmUgDEhTiw/Rr3eHdA8zHI/AAAAAAAAAX4/UN7u8DwlOBs/s1600-h/xda-salespackage%5B1%5D.jpg"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://1.bp.blogspot.com/_QCmUgDEhTiw/Rr3eHdA8zHI/AAAAAAAAAX4/UN7u8DwlOBs/s320/xda-salespackage%5B1%5D.jpg" alt="" id="BLOGGER_PHOTO_ID_5097474572882332786" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;I bought an XDA in 2001, which was the first touch-screen phone/pda with an internet connection. It was what you'd call an iPhone these days, although it had to do with a lot less marketing. So for me: six years have brought us better marketing and a multi-touch screen. Still... I'l probably buy an iPhone when they're actually available here. Not because I need it. Just because I'm an early adopter.&lt;br /&gt;&lt;br /&gt;As an early adopter you of course run the risk of buying things that will never catch on. Or becoming the involuntary beta tester of a device, which means the technology is not yet ready for prime time. One area where the latter happened to me is with wireless networking.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_QCmUgDEhTiw/Rr3g0dA8zLI/AAAAAAAAAYY/Fa-v6G-mL78/s1600-h/linksys_wap54g-400%5B1%5D.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_QCmUgDEhTiw/Rr3g0dA8zLI/AAAAAAAAAYY/Fa-v6G-mL78/s200/linksys_wap54g-400%5B1%5D.jpg" alt="" id="BLOGGER_PHOTO_ID_5097477544999701682" border="0" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_QCmUgDEhTiw/Rr3gWNA8zKI/AAAAAAAAAYQ/TW0ic4xXyVE/s1600-h/Quetec_Wireless_Access_Point_Card%5B1%5D.jpg"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://4.bp.blogspot.com/_QCmUgDEhTiw/Rr3gWNA8zKI/AAAAAAAAAYQ/TW0ic4xXyVE/s200/Quetec_Wireless_Access_Point_Card%5B1%5D.jpg" alt="" id="BLOGGER_PHOTO_ID_5097477025308658850" border="0" /&gt;&lt;/a&gt;I bought my first wireless access point and card somewhere in 2001. It wasn't completely new back then, but it hadn't been adopted by the masses yet. So usability and interoperability left something to be desired. USB wasn't as ubiquitous as it is now, so for a desktop PC I had to use a PCI to PCMCIA (now called PC-card and almost extinct) adapter. But hey... it worked... at times. But about half of the time it didn't work and I had to roll out a UTP cable again. I've tried to get a reliable wireless network over the years, but the devices either didn't work reliably or just broke down within a few months of service.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_QCmUgDEhTiw/Rr3bVNA8zFI/AAAAAAAAAXo/YxL6FInFx-U/s1600-h/119-5195%5B1%5D.jpg"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 150px; height: 101px;" src="http://4.bp.blogspot.com/_QCmUgDEhTiw/Rr3bVNA8zFI/AAAAAAAAAXo/YxL6FInFx-U/s200/119-5195%5B1%5D.jpg" alt="" id="BLOGGER_PHOTO_ID_5097471510570650706" border="0" /&gt;&lt;/a&gt;Somewhere in 2004 I just gave up on it and restored the cables to their full and permanent glory. So when whole tribes, states and even countries started using wireless networking, my house is completely wired. It's been like that for years now; first with really long UTP cables running down the hallways. They might not be pretty, but at least they work most of the time.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_QCmUgDEhTiw/Rr3hPtA8zMI/AAAAAAAAAYg/AcfsmOC2Cxs/s1600-h/dlan%5B1%5D.jpg"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://2.bp.blogspot.com/_QCmUgDEhTiw/Rr3hPtA8zMI/AAAAAAAAAYg/AcfsmOC2Cxs/s200/dlan%5B1%5D.jpg" alt="" id="BLOGGER_PHOTO_ID_5097478013151136962" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Two years ago I started using ethernet-over-powerline adapters, which use he powerline network in my house serves as a network. These adapters turned out to be as reliable as using direct UTP cables. So in my study I connect the ADSL modem/router to the powerline through one of these adapters. And in other rooms I connect computers to the powerline through another adapter. And it just works.&lt;br /&gt;&lt;br /&gt;The adapters are ridiculously expensive and not very rugged, but they do give me the true plug-and-play experience that I never got with wifi. And even though having blue adapters in many wall sockets is not very pretty, it's a lot better than all those colorful UTP cables lining the floor. So -although expensive- I was pretty happy with it. Most people may prefer wifi, I've been sticking with ethernet-over-powerline.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_QCmUgDEhTiw/Rr3dedA8zGI/AAAAAAAAAXw/_RyJRGfxwYs/s1600-h/DSC00295.JPG"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://1.bp.blogspot.com/_QCmUgDEhTiw/Rr3dedA8zGI/AAAAAAAAAXw/_RyJRGfxwYs/s200/DSC00295.JPG" alt="" id="BLOGGER_PHOTO_ID_5097473868507696226" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;A few weeks ago I saw a new device from Devolo: a wireless extender. So you plug this adapter into a socket and not only can you plug in an UTP cable, but it also provides wireless networking. So once again I couldn't resist and ordered one. If the whole world is using wireless without problems, I can't stay behind - can I?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-6723519643860230494?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/6723519643860230494/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=6723519643860230494' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/6723519643860230494'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/6723519643860230494'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2007/08/will-wireless-work.html' title='Will wireless work?'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_QCmUgDEhTiw/Rr3eHdA8zHI/AAAAAAAAAX4/UN7u8DwlOBs/s72-c/xda-salespackage%5B1%5D.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-7846199783572696846</id><published>2007-08-03T19:51:00.000+01:00</published><updated>2007-08-03T18:58:31.878+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript boolean logic'/><title type='text'>Both, either or any</title><content type='html'>I recently was adding functionality to a part of our product that was full of lines like this:&lt;br /&gt;&lt;ul style="font-family: courier new; list-style-type: none; list-style-image: none; list-style-position: outside;"&gt;&lt;li&gt;lBool_vert_in = this.IsBoathBitsSet(iArrAllow[key], iArrDeny[key],&lt;/li&gt;&lt;li&gt;   top.mTDSDefines_CheckInAction);&lt;/li&gt;&lt;/ul&gt;At first I was just annoyed by the obvious typo in there. I'm sure a boath is something (maybe a very expensive boat) but it has no place in our code. Making a typing error is not strange, it's actually quite normal. But not correcting it when you're copy/pasting it at least a few dozen times is a kind of laziness that I don't like very much.&lt;br /&gt;&lt;br /&gt;Later I also needed to know what the function does. At first I thought the name was actually pretty self-describing. But I couldn't explain the behavior I was seeing in a part of the code that invoked this function.&lt;br /&gt;&lt;ul style="font-family: courier new; list-style-type: none; list-style-image: none; list-style-position: outside;"&gt;&lt;li&gt;this.IsBoathBitsSet = function(iBit1Value, iBit2Value, iDefineType)&lt;/li&gt;&lt;li&gt;{&lt;/li&gt;&lt;li&gt;   return (&lt;/li&gt;&lt;li&gt;   (&lt;/li&gt;&lt;li&gt;      (iBit1Value &amp; iDefineType)||&lt;/li&gt;&lt;li&gt;      (iBit2Value &amp; iDefineType)&lt;/li&gt;&lt;li&gt;   ) == iDefineType )&lt;/li&gt;&lt;li&gt;}&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Now that strikes me as odd... the function name suggests that both bits need to be set. This would normally translate into an &amp;&amp;amp; and not into an || like in this code. So instead of just a simple typo, this function name is actually plain wrong. And it also immediately explained the behavior I was getting.&lt;br /&gt;&lt;br /&gt;I was thinking of renaming the function. But what shuld I rename it to? At first I was thinking IsEitherBitSet, since it returns true if either bit it set. But that is also not entirely correct. Since it also returns true if both bits are set.&lt;br /&gt;&lt;br /&gt;So maybe it should be called IsAnyBitSet. What do you think? How do you translate boolean operators into English?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-7846199783572696846?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/7846199783572696846/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=7846199783572696846' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/7846199783572696846'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/7846199783572696846'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2007/08/both-either-or-any.html' title='Both, either or any'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-4017409716099857826</id><published>2007-07-29T08:42:00.000+01:00</published><updated>2008-01-21T06:27:06.366+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Mac'/><category scheme='http://www.blogger.com/atom/ns#' term='TextMate'/><category scheme='http://www.blogger.com/atom/ns#' term='WinSCP'/><category scheme='http://www.blogger.com/atom/ns#' term='Fugu'/><title type='text'>Why is there no WinSCP for the Mac?</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_QCmUgDEhTiw/Rqw3b9A8y3I/AAAAAAAAAUY/WhOCt_ImmpA/s1600-h/icon_tigercd%5B1%5D.gif"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://2.bp.blogspot.com/_QCmUgDEhTiw/Rqw3b9A8y3I/AAAAAAAAAUY/WhOCt_ImmpA/s320/icon_tigercd%5B1%5D.gif" alt="" id="BLOGGER_PHOTO_ID_5092506232023731058" border="0" /&gt;&lt;/a&gt;Ever since Mac OSX came out, I understood that the Mac is the dream machine for software developers. It's a UNIX like system with a great GUI built on top of it. But how come then that even after I've owned an iMac for a few months, I find myself hardly doing any development work on it?&lt;br /&gt;&lt;br /&gt;I try to do my personal development work on the iMac. And recently I even found it quite nice for developing a Java application that's been in my head for a few years now. But most of the development work I do at home is not Java work, it's web development work. And for that I still find myself behind my trusty old Windows XP laptop.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_QCmUgDEhTiw/Rqw2c9A8y2I/AAAAAAAAAUQ/5T35rtllifo/s1600-h/winscp%5B1%5D.png"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://2.bp.blogspot.com/_QCmUgDEhTiw/Rqw2c9A8y2I/AAAAAAAAAUQ/5T35rtllifo/s320/winscp%5B1%5D.png" alt="" id="BLOGGER_PHOTO_ID_5092505149691972450" border="0" /&gt;&lt;/a&gt;The reason for that is that I do most work through the remote editing feature of WinSCP. For those of you that don't know that program: it provides a Norton Commander style (dual pane) interface, with one pane being the local system and the other a remote system. Like a remote web server. And with a single key press you can open any remote file in a very simple local text editor, make a few changes and then save the file back. Their embedded editor might not be the most feature rich IDE, but it just works, it's there when I need it and apparently it's good enough for me to get the job done with.&lt;br /&gt;&lt;br /&gt;But then why doesn't a WinSCP exists for the Mac? I know it's called WinSCP, so it's for Windows. But the Mac must have something similar, right? Well, similar yes... On the Mac there is Fugu, which at first sight seems similar. But it's completely not the same for my case.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_QCmUgDEhTiw/Rqw4JNA8y5I/AAAAAAAAAUo/0NXm9CM1V_0/s1600-h/fugu%5B1%5D.png"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 107px; height: 61px;" src="http://3.bp.blogspot.com/_QCmUgDEhTiw/Rqw4JNA8y5I/AAAAAAAAAUo/0NXm9CM1V_0/s200/fugu%5B1%5D.png" alt="" id="BLOGGER_PHOTO_ID_5092507009412811666" border="0" /&gt;&lt;/a&gt;Fugu doesn't come with a built-in editor and to me that makes a lot of a difference. They chose to integrate with existing editors instead. Which wouldn't be a problem, if they'd actually integrate with the standard OSX editor: TextEdit. But they don't. Instead they offer a whole list of more or lesser known editors, from BBEdit via TextMate to VI and emacs.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_QCmUgDEhTiw/Rqw3xdA8y4I/AAAAAAAAAUg/iXhdJ42Acrg/s1600-h/2007-03-20_textmate%5B1%5D.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://4.bp.blogspot.com/_QCmUgDEhTiw/Rqw3xdA8y4I/AAAAAAAAAUg/iXhdJ42Acrg/s320/2007-03-20_textmate%5B1%5D.png" alt="" id="BLOGGER_PHOTO_ID_5092506601390918530" border="0" /&gt;&lt;/a&gt;Unfortunately I don't have about 75% of the editors Fugu does integrate with. TextMate sounds great, but I haven't bought it yet as I'll need to invest some time to learn to appreciate it. And for the editors that I do have and that Fugu supports, their integration doesn't work. Now if it doesn't work for those editors, do I want to risk installing yet another editor on my system to find out if the integration works there? Well apparently I don't.&lt;br /&gt;&lt;br /&gt;While typing this I already noticed that there are of course more options than just Fugu. There's Cyberduck, apparently Krusader also is nice and Disk Order sounds perfect if it would support SCP/SFTP. So I have some options ahead of me. But until I invest the time and find something that works at least as well as WinSCP, my development work on the Mac is limited to Java applications.&lt;br /&gt;&lt;br /&gt;--- January 21, 2008 - Frank van Puffelen ---&lt;br /&gt;&lt;br /&gt;I might have finally found my WinSCP replacement. Read more about it &lt;a href="http://frank.vanpuffelen.net/2008/01/winscp-replacement-for-mac.html"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-4017409716099857826?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/4017409716099857826/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=4017409716099857826' title='17 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/4017409716099857826'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/4017409716099857826'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2007/07/why-is-there-not-winscp-for-mac.html' title='Why is there no WinSCP for the Mac?'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_QCmUgDEhTiw/Rqw3b9A8y3I/AAAAAAAAAUY/WhOCt_ImmpA/s72-c/icon_tigercd%5B1%5D.gif' height='72' width='72'/><thr:total>17</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2341502915119631850.post-4337326425493984889</id><published>2007-07-24T20:28:00.000+01:00</published><updated>2007-07-24T19:30:16.147+01:00</updated><title type='text'>Don't mistake the means for the goal</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_QCmUgDEhTiw/RqZEH9A8ytI/AAAAAAAAAS4/M661m2ljxUs/s1600-h/stacks%2520of%2520money%5B1%5D.jpg"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 140px; height: 127px;" src="http://4.bp.blogspot.com/_QCmUgDEhTiw/RqZEH9A8ytI/AAAAAAAAAS4/M661m2ljxUs/s320/stacks%2520of%2520money%5B1%5D.jpg" alt="" id="BLOGGER_PHOTO_ID_5090831332217244370" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Last weekend my wife was balancing her checkbook. Not that we have a problem making ends meet, but after an embarrassing situation at an ATM she really wanted to figure out why these things sometimes happen to her. I agreed to help her, as long as she would do the actual work herself and draw her own conclusions.&lt;br /&gt;&lt;br /&gt;As it quickly turned out, she had no idea where her salary is going. So I suggested she'd categorize her spendings based on what she could find in the tele-banking application. She went to work on it. I knew that something was going wrong when I entered her room three hours later and found her still busy categorizing.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_QCmUgDEhTiw/RqZETtA8yuI/AAAAAAAAATA/hTiwWOPk_CM/s1600-h/checkbook%5B1%5D.jpg"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 203px; height: 152px;" src="http://3.bp.blogspot.com/_QCmUgDEhTiw/RqZETtA8yuI/AAAAAAAAATA/hTiwWOPk_CM/s320/checkbook%5B1%5D.jpg" alt="" id="BLOGGER_PHOTO_ID_5090831534080707298" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;It turned out that she had found this online checkbook application, which could show all kinds of charts based on your spendings. All you had to do was upload a dump of the tele-banking information and categorize it using their "super friendly" web interface. Three hours later and she still wasn't done categorizing just a few months worth of spendings.&lt;br /&gt;&lt;br /&gt;I asked my wife whether this was really worth the time. After all, categorizing spendings was not the goal. It was just supposed to be a means to quickly figure out where her money was going. She assured me that it was worth it; she was almost done and then she would know.&lt;br /&gt;&lt;br /&gt;An hour and a half later she was indeed done and proudly called me in. "See. I spent this much on our Holidays. And that much on gas." Very interesting information I'm sure, but not what I was interested in. "I'm pretty sure I also paid part of those Holidays", I said. "Does this mean that I didn't pay my fair share?" She started clicking on the charts frantically. "It must be in there?" She couldn't find it and had to go back to the tele-banking application to look it up through a quick search.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_QCmUgDEhTiw/RqZE5tA8yvI/AAAAAAAAATI/QLWDotjoWf4/s1600-h/atm3400%5B1%5D.jpg"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://3.bp.blogspot.com/_QCmUgDEhTiw/RqZE5tA8yvI/AAAAAAAAATI/QLWDotjoWf4/s320/atm3400%5B1%5D.jpg" alt="" id="BLOGGER_PHOTO_ID_5090832186915736306" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Forty-five minutes later we had pretty much figured out where her money is going. And although the online checkbook gave some nice charts, we frequently had to go back to the "source" (the tele-banking application) for additional details. The online checkbook seemed like a nice, quick way of doing the categorizing. But when it turned out it wasn't very fast, she should have stopped entering data into it. Categorizing the spendings was just a means to figure out what type of things her money was being spent on, it wasn't the goal.&lt;br /&gt;&lt;br /&gt;It turned out that I indeed didn't pay my fair share of the Holidays and that she was paying for some insurances that really should come from our shared account. With that and a solid resolution to determine where all the cash withdrawals are going she's pretty sure that those embarrassing ATM incidents should not occur anymore. Or at least not too frequently...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2341502915119631850-4337326425493984889?l=frank.vanpuffelen.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://frank.vanpuffelen.net/feeds/4337326425493984889/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=2341502915119631850&amp;postID=4337326425493984889' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/4337326425493984889'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2341502915119631850/posts/default/4337326425493984889'/><link rel='alternate' type='text/html' href='http://frank.vanpuffelen.net/2007/07/dont-mistake-means-for-goal.html' title='Don&apos;t mistake the means for the goal'/><author><name>Frank</name><uri>http://www.blogger.com/profile/13688437747795053947</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='13832203409704742423'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_QCmUgDEhTiw/RqZEH9A8ytI/AAAAAAAAAS4/M661m2ljxUs/s72-c/stacks%2520of%2520money%5B1%5D.jpg' height='72' width='72'/><thr:total>0</thr:total></entry></feed>