<?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:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-15558048</id><updated>2012-01-10T06:16:43.174-05:00</updated><category term='ruby'/><category term='pair programming'/><category term='LightMeter'/><category term='RSpec'/><category term='Photography Assistant'/><category term='iPad2'/><category term='unit test'/><category term='browser'/><category term='xoom'/><category term='internet'/><category term='pair switch'/><category term='browsing'/><category term='design'/><category term='Photography'/><category term='tdd'/><category term='readability'/><category term='iPad'/><category term='testing'/><category term='Android'/><category term='extreme programming'/><category term='honeycomb'/><title type='text'>Kai - blah blah blah</title><subtitle type='html'>Thoughts about software and technology in general</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>57</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-15558048.post-5332666292691533228</id><published>2011-12-08T20:48:00.014-05:00</published><updated>2011-12-09T18:30:29.459-05:00</updated><title type='text'>Test first != TDD</title><content type='html'>Recently I've seen a misunderstanding of TDD(test driven development), that is, if you always write test first and write code that passes that test, and voilà, you are TDDing, everything will be better now.  That is not TDD. That is 100%-test-coverage-driven-development. &lt;br /&gt;&lt;h3&gt;TDD is &lt;b&gt;NOT&lt;/b&gt; about achieving 100% test coverage (which IMO is meaningless). &lt;/h3&gt;&lt;br /&gt;&lt;h3&gt;TDD is about using test to &lt;b&gt;drive&lt;/b&gt; development. &lt;/h3&gt;&lt;br /&gt;More specifically, TDD requires three equally important types of activities:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;   &lt;li&gt; writing test &lt;/li&gt;&lt;br /&gt;   &lt;li&gt; writing the simplest code that passes the test&lt;/li&gt;&lt;br /&gt;   &lt;li&gt; refactoring code that keeps tests passing &lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;A common misunderstanding is that the refactoring step is optional. It's &lt;b&gt;not&lt;/b&gt;. &lt;br /&gt;The following is not TDD.&lt;br /&gt;First the test:&lt;br /&gt;&lt;pre class='prettyprint'&gt;&lt;br /&gt;   describe 'alert' do&lt;br /&gt;      it 'should log the message' do &lt;br /&gt;         logger.should_receive(:info).with('a message');&lt;br /&gt;         subject.alert('a message');&lt;br /&gt;      end&lt;br /&gt;   end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then the method in tested class:&lt;br /&gt;&lt;pre class='prettyprint'&gt;&lt;br /&gt;    def alert(msg)&lt;br /&gt;       logger.info(msg)&lt;br /&gt;    end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then you are done with this method. This is NOT TDD. &lt;br /&gt;First, you didn't refactor the code. Second, it's not likely that the test will help when you need to refactor this code in the future. In one sentence, the refactor-and-keep-tests-green part is missing.  &lt;br /&gt;So what will a TDDer do here? Simply write the code without the test. A typical argument against it is that &lt;i&gt;what if some other developer accidentally delete the logger.info(msg) code&lt;/i&gt;?  Well, that's not the purpose of TDD, that is what version control is for. &lt;br /&gt;&lt;br /&gt;In fact, it can be argued that this test is a form of code duplication. It's just that this duplication is very easy to detect (if you change one place without the other, the test will break.)&lt;br /&gt;Things could be a bit different if your code is like this.&lt;br /&gt;&lt;pre class='prettyprint'&gt;&lt;br /&gt;   describe 'alert' do&lt;br /&gt;      it 'should log the title and message' do &lt;br /&gt;         logger.should_receive(:info).with('a title: a message');&lt;br /&gt;         subject.alert('a title', 'a message');&lt;br /&gt;      end&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;   def alert(title, msg)&lt;br /&gt;      logger.info(title + ': ' + msg)&lt;br /&gt;   end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;There is a possibility that you need to refactor to improve the performance of the string concatenation. In this case, the test here has some value. &lt;br /&gt;&lt;h3&gt;So, the purpose of tests in TDD is to facilitate refactoring. &lt;/h3&gt; In many cases, tests give you the ability to refactor code with the confidence that it won't break functionality. In other cases, you may have to change some tests to do your refactoring, because unless it's a real black box end to end test, you are always going to have some form of logic duplication in your tests. &lt;br /&gt;So a test is probably helpful for some refactoring but impeding for some other refactoring. &lt;br /&gt;&lt;h3&gt;The value of a test is determined by the likelihood of it helping refactoring v.s. the likelihood of it impeding it. &lt;/h3&gt;&lt;br /&gt;When writing tests in TDD, we need to maximize this value with as less cost as possible. Tests on constructors, accessors, constants and simply delegations are typical examples of test with zero or negative value. Pure interaction tests are likely to have less value than a more end-to-end test but it should be evaluated on case-by-case basis. &lt;br /&gt;You also need to take cost into consideration - for example, end-to-end test might takes more effort to write and/or more resource to run. &lt;br /&gt;&lt;br /&gt;There is no silver bullet. Always-write-test-first isn't one. It takes some effort to determine when to write tests and how to do so, but it's not too hard - just focus on the value of the test, that is, how much benefits it can bring to immediate and foreseeable refactoring.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-5332666292691533228?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/5332666292691533228/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=5332666292691533228' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5332666292691533228'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5332666292691533228'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2011/12/test-first-tdd.html' title='Test first != TDD'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-5348140918899997488</id><published>2011-09-22T12:50:00.012-04:00</published><updated>2011-09-23T00:56:17.655-04:00</updated><title type='text'>queffee.js - a dynamic priority worker queue in javascript/coffeescript</title><content type='html'>Recently I wrote a very simple javascript library that helps manage asynchronous tasks. I named it queffee (&lt;a href='http://github.com/kailuowang/Queffee'&gt;github&lt;/a&gt;).  It was written in coffeescript but the compiled javascript file is available too. This article gives an introduction about this library and how it can help a web application on the client side.&lt;br /&gt;&lt;br /&gt;queffee.js is a small library and for the most part it has two public classes: queffee.Q - a dynamic priority job queue and queffee.Worker - well, a worker. You can enqueue tasks (asynchronous functions) into the queue and start a worker, then the worker will run those functions one by one. You can also start multiple workers can they will work on multiple tasks in parallel (in a single thread).&lt;br /&gt;&lt;br /&gt;As an example ajax web application, let's look at a picture slide show running in a browser.&lt;br /&gt;Let's start with the basic usage. In the slide show, there are many picture objects, which have a preload function that preloads the picture content to the browser so that when user select to display them, they will be immediately ready. This preload function is asynchronous and takes in a callback to call when the preload is done.&lt;br /&gt;&lt;pre class='prettyprint'&gt;&lt;br /&gt;  class Picture&lt;br /&gt;    preload: (callback) =&gt;&lt;br /&gt;      #preloading itself in browser, callback on success&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You can always simply do&lt;br /&gt;&lt;pre class='prettyprint'&gt;&lt;br /&gt;  for pic in pictures&lt;br /&gt;    picture.preload()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The issue here is that because preload is asynchronous, this will run all of them simultaneously, so if you have too many pictures, that might be simply too many requests to the server. You can better manage that using queffee.&lt;br /&gt;&lt;pre class='prettyprint'&gt;&lt;br /&gt;    q = new queffee.Q&lt;br /&gt;    worker = new queffee.Worker(q)&lt;br /&gt;    for pic in pictures&lt;br /&gt;      q.enQ picture.preload&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The worker will preload picture one after another, so one request at a time. If you want more simultaneous connections you can always start more workers.&lt;br /&gt;&lt;pre class='prettyprint'&gt;&lt;br /&gt;  anotherWorker = new queffee.Worker(q)&lt;br /&gt;  #or&lt;br /&gt;  _(4).times -&gt; new queffee.Worker(q)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now let's look at a more advanced use case. In a slide show, users might select to skip some pictures and jump to a picture further down in slide. So preloading the pictures they already skipped is probably not as important as preloading the pictures immediately after the one they are seeing now. So we need to prioritize the preloading according to the current progress of the user. queffee supports that. When you enQ, you can give it a priority function and it will be used for the priority queue. Let's say you added a priority function to your picture class&lt;br /&gt;&lt;pre class='prettyprint'&gt;&lt;br /&gt;  class Picture&lt;br /&gt;    priority: =&gt;&lt;br /&gt;      #calculates its priority according to the distance to the current progress&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;then you can enQ the preloads like the following&lt;br /&gt;&lt;pre class='prettyprint'&gt;&lt;br /&gt;   q.enQ picture.preload, picture.priority&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now when users skip pictures, you just need to call&lt;br /&gt;&lt;pre class='prettyprint'&gt;&lt;br /&gt;   q.reorder()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then the preloading works will be re-prioritized according to the new slideshow progress.&lt;br /&gt;If you don't need to dynamically prioritize the tasks, you can also pass in a number value as the priority.&lt;br /&gt;&lt;br /&gt;So far so good right? Another really interesting usage of queffee is to support offline mode for you ajax application. &lt;br /&gt;Back to the picture slide show as the example. With enough preloaded pictures, it should be able to work fine after the browser goes offline. However, like most other ajax web apps, even with the data available locally, it still needs to update state back to server. Specifically in this app, if user operated on the picture, it needs to update the server with the latest states. More specifically, it needs to send ajax put/post requests back to the server. We don't really need a response from the server but if the browser goes offline, such ajax requests will be lost. With queffee, the problem can be elegantly solved with the following code.&lt;br /&gt;&lt;pre class='prettyprint'&gt;&lt;br /&gt;  class Updater&lt;br /&gt;    constructor: -&gt;&lt;br /&gt;      @_q = new queffee.Q&lt;br /&gt;      @_worker = new queffee.Worker(@_q)&lt;br /&gt;&lt;br /&gt;    put: (url, data) =&gt; this._addJob('put', url, data)&lt;br /&gt;&lt;br /&gt;    post: (url, data) =&gt; this._addJob('post', url, data)&lt;br /&gt;&lt;br /&gt;    _addJob: (method, url, data) =&gt;&lt;br /&gt;      @_q.enQ (callback) =&gt;&lt;br /&gt;        $.ajax(&lt;br /&gt;                url: url&lt;br /&gt;                data: data&lt;br /&gt;                type: type&lt;br /&gt;                success: callback&lt;br /&gt;                error: =&gt; setTimeout(@_worker.retry, 180000)&lt;br /&gt;              )&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then just use this updater to do the ajax update&lt;br /&gt;&lt;pre class='prettyprint'&gt;&lt;br /&gt;  class Picture&lt;br /&gt;    update: =&gt; updater.put('/picture', this)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;That's it. The picture.update() methods is offline safe now. The Updater class uses queffee to run the ajax requests one by one. If disconnection causes one ajax request to fail, it will automatically retry every 3 minutes until it succeeds and continue with the following one. The way it detects the disconnection is a bit naive here but you got the idea.  &lt;br /&gt;There are two not so obvious things here: 1) the worker will not proceed to the next job until the current job finishes and calls callback, so if the ajax call in the current job fails, the worker will stay with the current job 2) the worker.retry() function re-run current job again which if succeeds, will start moving things again. &lt;br /&gt;&lt;br /&gt;Be default queffee.Worker waits on the asynchronous task indefinitely, but if you give it a ms timeout (value or function), it will move on the next task after timeout.&lt;br /&gt;For example, the following code waits at most 10 seconds for the picture to finish reload, after which the next picture will get preloaded.&lt;br /&gt;&lt;pre class='prettyprint'&gt;&lt;br /&gt;   q.enQ picture.preload, picture.priority, 10000&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In some cases you have a collection of asynchronous tasks and you want to run all of them sequentially and meanwhile monitor the progress. I wrote a util class in queffee called CollectionWorkQ just for that.&lt;br /&gt;Here is the usage, let's still take the preloading pictures as the example, except this time we don't care about the priority but we need to monitor the progress.&lt;br /&gt;&lt;pre class='prettyprint'&gt;&lt;br /&gt;    new queffee.CollectionWorkQ(&lt;br /&gt;      collection: pictures&lt;br /&gt;      operation: 'preload'&lt;br /&gt;      onProgress: -&gt; alert('another picture ready!')&lt;br /&gt;      onFinish: -&gt; alert('finished')&lt;br /&gt;    ).start()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This should be intuitive enough, the operation option takes in a function name that will be called on each item of the collection. The operation option can also be a function that takes in an item and a callback like the following&lt;br /&gt;&lt;pre class='prettyprint'&gt;&lt;br /&gt;    new queffee.CollectionWorkQ(&lt;br /&gt;      collection: pictures&lt;br /&gt;      operation: (picture, callback) -&gt; picture.preload(callback)&lt;br /&gt;    ).start()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That's it. Thanks for reading this. I hope it can be of usage for your projects. Again, it's on &lt;a href='http://github.com/kailuowang/Queffee'&gt;github&lt;/a&gt;, any contribution will be great.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-5348140918899997488?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/5348140918899997488/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=5348140918899997488' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5348140918899997488'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5348140918899997488'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2011/09/queffeejs-dynamic-priority-worker-queue.html' title='queffee.js - a dynamic priority worker queue in javascript/coffeescript'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-1378362991714255866</id><published>2011-08-24T19:22:00.014-04:00</published><updated>2011-09-26T14:54:54.692-04:00</updated><title type='text'>Why Coffeescript's fat arrow makes it more functional (than JS)</title><content type='html'>We all know that in JS &lt;span style="font-style:italic;"&gt;this&lt;/span&gt; is not read from the scope chain, it's reset on a context by context basis. Experienced JS programmers work around it and are quite used to it, but I think this fact made JS a lot less beautiful than it should be.  Here is a simple example&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;var Note = Backbone.Model.extend({&lt;br /&gt;   initialize: function() {},&lt;br /&gt;   delete: function() {&lt;br /&gt;      //do something&lt;br /&gt;      this.log('deleted');&lt;br /&gt;   },&lt;br /&gt;   log: function(msg) { &lt;br /&gt;      alert(msg); &lt;br /&gt;   }&lt;br /&gt;});&lt;br /&gt;var note = new Note();&lt;br /&gt;&lt;br /&gt;$('#delete-note').click(function(){ &lt;br /&gt;    note.delete();&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Sure, backbone.js already makes things a lot cleaner but the itchy point is this &lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt; $('#delete-note').click(function(){ &lt;br /&gt;      note.delete();&lt;br /&gt; });&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The fact that you have to wrap the event handler within a function just make it more like a block passed in than what it really is - a function, especially when you are from a ruby background. &lt;br /&gt;It really should just be &lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;$('#delete-note').click(note.delete);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Well, you can't. If you do that, all the 'this' will be of a different meaning in the body of the delete function - in our example, it will complain that the log is undefined for HTMLElement. &lt;br /&gt;Here is what Coffeescript gives you with its wonderful fat arrow &lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;class Note extends Backbone.Model&lt;br /&gt;   delete: =&gt;&lt;br /&gt;      #do something&lt;br /&gt;      this.log('deleted')&lt;br /&gt;   log: (msg) =&gt; &lt;br /&gt;      alert(msg)&lt;br /&gt;&lt;br /&gt;note = new Note&lt;br /&gt;$('#delete-note').click  note.delete&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;At a glance, it might look like just some keystroke savings. But from my experience the mind set that you can just pass any function as variables around is what makes it a lot more FUNctional.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-1378362991714255866?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/1378362991714255866/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=1378362991714255866' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/1378362991714255866'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/1378362991714255866'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2011/08/why-coffeescripts-fat-arrow-makes-it.html' title='Why Coffeescript&apos;s fat arrow makes it more functional (than JS)'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-7282517338355384234</id><published>2011-08-02T19:53:00.009-04:00</published><updated>2011-08-02T20:50:41.530-04:00</updated><title type='text'>Interesting notes for a Rails 3.1 performance issue on an EC2 micro instance</title><content type='html'>For my web app, I recently switched to an Ajax intensive model where most of the html manipulation happens on client side with coffeescript. This significantly increases the number of http requests hitting the server, which caused really serious performance problem on my micro instance on EC2. The app reacts quickly for the first page but then would literally freeze for 1-2 minutes.  It turned out to be a combination of the way how Ajax app, Passenger and EC2 micro instance works. &lt;br /&gt;&lt;br /&gt;With default configuration, Passenger tends to keep a low number of instances in the pool and spawn when it needs to take care of higher volume of requests. It also recycles unused instance regularly (every 5 minutes by default). It works perfectly with the one request takes care everything model, but an Ajax app tends to fire more ajax calls and they usually come in spikes. &lt;br /&gt;Passenger spawn more instance with the assumption that these requests are coming from multiple users and it needs to guarantee a reasonable response time for all of them.  It's a waste for an Ajax model because the asynchronized nature of Ajax application allows them to function reasonably without a super fast server response on every request. Spawning extra instances becomes an overhead when serving a small number of users. Because the normal pattern for an Ajax application is that passenger sees a burst of requests and spawns a bunch of instances and then recycle most of them when user stops operating the app for a bit. Spawning apps is a CPU intensive operation. &lt;br /&gt;&lt;br /&gt;On EC2 micro instance this became disastrous because these instances throttles your CPU after a cpu usage spike to prevent you from using 100% CPU all the time(they are shared). The consequence is that every time Passenger spawns 6 instance (default config value), EC2 'helps' by throttling you CPU to only 2.7% of normal computation power... &lt;br /&gt;&lt;br /&gt;My solution to this is that I fixed the number of instances in the passenger instance pool at a lower level. This way, ajax calls might get queued a bit from time to time but 1) hardly felt at the client side because most of the ajax calls are some proactive data pre-fetching and 2) no CPU usage spikes.&lt;br /&gt;&lt;br /&gt;The lesson I learned from this is that a lots of rethinking is needed in many aspects of web app when we switch to the Ajax driven model.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-7282517338355384234?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/7282517338355384234/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=7282517338355384234' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7282517338355384234'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7282517338355384234'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2011/08/interesting-notes-for-rails-31.html' title='Interesting notes for a Rails 3.1 performance issue on an EC2 micro instance'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-3851763494016996203</id><published>2011-07-13T23:08:00.008-04:00</published><updated>2011-07-14T00:35:57.599-04:00</updated><title type='text'>Google, please create a real market for web apps.</title><content type='html'>The chrome web store is great, but comparing to the Android market place or the Apple app store, it's almost negligible. Why? Are there less people using web? Or are there less web app developers? Obviously neither of those are true. What Google really should do about the chrome web store is to focus on the supply side and make it as much like the mobile app eco-systems as possible.&lt;br /&gt;&lt;br /&gt;One of the major reasons mobile app stores exploded is that there is minimum cost to deliver an innovative idea into a product immediately available to the mass, (well at least for those ideas that does not require a centralized service).  All you need to do is to write the code. There is minimum infrastructure needed at your side to create and distribute the product. Your revenue management is also taken care of. You can almost completely focus on your code. I think this is the key to innovation explosion on mobile platforms. &lt;br /&gt;&lt;br /&gt;For the web apps, things are a lot more complicated. Writing the code is arguably more enjoyable than the mobile app. (RoR vs Objective-C or Swing like Java), but then you also need to do 3 things&lt;br /&gt;1) get a server infrastructure and setup the environment. &lt;br /&gt;2) create a store front - either put it on the chrome web store or maintain your own marketing website which is harder to get mass attention. &lt;br /&gt;3) setup your revenue flow because your server infrastructure is going to cost you money. you can either embed some ads service, or you can create a subscription system and integrate with some 3rd party online payment service. &lt;br /&gt;&lt;br /&gt;None of these 3 things are trivial. They make innovation on the web platform significantly harder than on the mobile platform. &lt;br /&gt;&lt;br /&gt;Being a web adds company, Google is probably the only company that has the incentive and resource to push for an innovation explosion on the web platform. I am proposing that Google should create a web app platform with integrated infrastructure/marketing/revenue services. Let product developers focus on product development and this platform takes care the rest: &lt;br /&gt;1) an elastic and flexible cloud computing platform that is optimized for web app deployment. Technological wise, though, there should be as less limitations as possible. It should be more like EC2 less like app engine.&lt;br /&gt;2) Google already has this - the chrome web store. Make it more prominent rather than just a chrome browser/os concept. &lt;br /&gt;3) A revenue sharing system to cover the cost of the cloud computing. To conform to Google's free service for ads revenue philosophy, this revenue sharing system is probably ads based. It can start from a single limited cloud computing resource (like the free EC2 micro instance.) Once the usage volume and the ads revenue increase to a certain level, higher tier of resource will be 'unlocked' for the product to allow even larger usage volume and ads revenue. &lt;br /&gt;&lt;br /&gt;The idea is that there should be a minimum entrance cost to this web market just like the Android market and Apple app store.  People can just create prototype product of their innovative ideas and throw it to the market to test the reaction. How exciting that will be.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-3851763494016996203?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/3851763494016996203/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=3851763494016996203' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/3851763494016996203'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/3851763494016996203'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2011/07/google-please-create-real-market-for.html' title='Google, please create a real market for web apps.'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-7427030069232180172</id><published>2011-06-04T15:41:00.004-04:00</published><updated>2011-06-04T16:42:50.573-04:00</updated><title type='text'>A readability monitor IDE plugin?</title><content type='html'>In large projects, developers spend a lot of time figuring out what is going on with the code before they can add functionality to it. For code base with poor readability, this activity cost a lot more and has to be repeated by multiple developers and sometimes the same developer multiple times (because human memory is short).  &lt;br /&gt;&lt;br /&gt;Is it possible to monitor the readability and its impact to the efficiency to the team? It might be achievable through monitoring the behavior of developers using development tools such as IDEs. &lt;br /&gt;There are mainly two modes in which a user engages with an IDE &lt;br /&gt;1) code browsing - user browse across the code base, quickly navigate between files and different parts of the file, during which user changes nothing. &lt;br /&gt;2) code writing - user writes code, usually focusing on a small set of locations in the code base. &lt;br /&gt; &lt;br /&gt;It shouldn't be too hard for an IDE plugin to differentiate the mode the user is currently in. For example, if the user hasn't change any code for a while but has been actively navigating thru the code base (such as switching between files, scrolling within them, searching and so on), he is probably in the reading mode. Otherwise if the user has been frequently writing code and running tests, he is probably in the writing mode. The plugin can then simply record the time period user spent in either modes, and provide reports of such behavior across the team. &lt;br /&gt;&lt;br /&gt;Obviously, the ratio between the code reading activity and the code writing activity across the team would be an good indicator of the overall readability of the code base. Of course sometimes such indicator might be redundant since the quality of code might be measurable by other means. However this indicator can also provide (to the people with the money) some quantifiable cost of bad code, or in another word, technical debt. You can say something like 95% of the development coding effort is wasted in reading this spaghetti code base v.s. the same team only spend 50% on another similar sized project. You can also suggest something like if we spend X weeks, we can improve the code base so that we can improve the code writing efficiency by Y percentage for the Z weeks of future development.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-7427030069232180172?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/7427030069232180172/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=7427030069232180172' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7427030069232180172'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7427030069232180172'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2011/06/readability-monitor-ide-plugin.html' title='A readability monitor IDE plugin?'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-8670249888995585549</id><published>2011-05-18T00:33:00.004-04:00</published><updated>2011-05-18T01:12:27.289-04:00</updated><title type='text'>Why community driven open source teams produce high quality software?</title><content type='html'>A community driven open source project is an open source project that is completely driven by an online community of developers where there is no organizational structure. Developers are contributing on their own time without getting paid except maybe donation. Also, they are free to leave the project at any point.  Why would such projects produce many times higher quality software than closed source software project developed by teams with scientific management and stringent accountability? &lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;br /&gt;No politics. Everyone is truly for the good of the project, nothing else. For example if someone feels that a refactoring is needed, he will just do it without worrying if he should focus on features development so that velocity looks good.    &lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;br /&gt;Better recruiting process: 1) only people really love the project will try to get on the project, 2) skill can be tested again and again thru the patches new candidates submit. In another sentence, candidates are evaluated by the real work they do on the project and no bias of any kind. This process can take as long as needed since in the same time the candidates are contributing the project.&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;br /&gt;Developers are forced to focus on good code quality. Because every single line of code needs to be highly consumable by other committers who, when they need to continue this work, usually don't have the context and may never get the chance to talk to the developer who wrote it. Readability and tests are absolute must-haves, because that's all other developers can count on.   &lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;br /&gt;More sense of achievement because the well designed code can be appreciated by a much broader audience in an open source project. Some say that programming is like art (and I believe they are talking about the code not the front end UI), then artists certainly appreciate more recognition of their work.&lt;br /&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/15558048-8670249888995585549?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/8670249888995585549/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=8670249888995585549' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/8670249888995585549'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/8670249888995585549'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2011/05/why-community-driven-open-source-teams_18.html' title='Why community driven open source teams produce high quality software?'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-5770561554599740884</id><published>2011-04-29T23:59:00.000-04:00</published><updated>2011-04-30T00:00:15.517-04:00</updated><title type='text'>Extreme Agile Development?</title><content type='html'>What if we push all the agile practices to the extreme just like Extreme Programming does? What will extreme agile development look like?&lt;br /&gt;If automated test is good, why not write acceptance criteria in automated test suites?If empowering the devs is good why not let them own the story too? &lt;br /&gt;If close coordination between QA and Dev is good why not let them pair all the time?&lt;br /&gt;If frequent and direct feedback from end users is good why not continuously deliver to them and collect feedback from them in an easy and automated way? &lt;br /&gt;&lt;br /&gt;So maybe this is what extreme agile development process look like:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Provide end users a way to easily submit their feedback to application's feedback database. &lt;/li&gt; &lt;br /&gt;&lt;li&gt;Product manager(s) categorize user feedback into high level feature requests and prioritize them. &lt;/li&gt;&lt;br /&gt;&lt;li&gt;Dev pair and QA work together (tripling) to design the detail user experience story for the high level feature request (together with the original end user feedback) and write the acceptance criteria in automated test suite such as cucumber. &lt;/li&gt;&lt;br /&gt;&lt;li&gt;Product manager(s) reviews the implementation design and the cucumber tests. (optional)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Dev pair develop the story to pass the automated test suites. During which if the test suites need to be changed, the QA will be pulled in. &lt;/li&gt;&lt;br /&gt;&lt;li&gt;Continuously deliver code to end users - deploy code to production as frequent as possible.       &lt;/li&gt;&lt;br /&gt;&lt;li&gt;Product managers keep monitoring end user feed back and ensure that all team members being on target with their user experience design&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Is this possible?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-5770561554599740884?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/5770561554599740884/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=5770561554599740884' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5770561554599740884'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5770561554599740884'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2011/04/extreme-agile-development.html' title='Extreme Agile Development?'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-7826326487785336109</id><published>2011-04-26T22:32:00.006-04:00</published><updated>2011-04-26T23:00:57.832-04:00</updated><title type='text'>Introducing Collectr - my first flickr App</title><content type='html'>Alpha test: &lt;a href="http://collectr.kailuowang.com"&gt;http://collectr.kailuowang.com/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h5&gt; What is Collectr? &lt;/h5&gt; Collectr is a web app that helps you, a flickr addicted, subscribe and explore flickr photos in a much more powerful and personalized way.&lt;br /&gt;&lt;h5&gt; What's the vison of Collectr?&lt;/h5&gt; To achieve what flickr explore failed to achieve - an easy and free way to see more interesting pictures every day.&lt;br /&gt;&lt;h5&gt; Why use Collectr? &lt;/h5&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight:bold;"&gt;Centralized slide show&lt;/span&gt;&lt;/li&gt;New photos from multiple sources will be displayed at a single slide show&lt;br /&gt;&lt;li&gt;&lt;span style="font-weight:bold;"&gt;Personalized slide show&lt;/span&gt;&lt;/li&gt;Collectr remembers your preference by recording your 'fave' action, so that when you have too many new photos from sources,&lt;br /&gt;it will display first the photos from the stream swhose photos you fave the most in the past.  &lt;br /&gt;&lt;li&gt; &lt;span style="font-weight:bold;"&gt;Expendable Sources of Photos&lt;/span&gt; &lt;/li&gt;With Collectr, not only can you subscribe to someone's upload stream, you can also subscribe to her favorites stream.&lt;br /&gt;This way you can discover flickr artists that are discovered by your favorite flickr artists.&lt;br /&gt;This is very important because it allows you to expand your list of sources of good photos.&lt;br /&gt;&lt;li&gt;&lt;span style="font-weight:bold;"&gt; Slide show with the best possible image quality &lt;/span&gt;&lt;/li&gt; Unlike many of the third party flickr websites, when the collectr slide show display photos, it will try find the version of the photos whose reslution fits your screen the most. Also, this slide show is tablet friendly with navigation keys on the sides and links using larger font.&lt;br /&gt;&lt;li&gt; &lt;span style="font-weight:bold;"&gt;Easy share &lt;/span&gt;&lt;/li&gt; You can share/backup your collections by exporting them into a backup file so that later you or other Collectr users can import it.&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-7826326487785336109?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/7826326487785336109/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=7826326487785336109' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7826326487785336109'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7826326487785336109'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2011/04/introducing-collectr-my-first-flckr-app.html' title='Introducing Collectr - my first flickr App'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-2372280160646152285</id><published>2011-04-11T20:15:00.003-04:00</published><updated>2011-04-11T20:57:35.907-04:00</updated><title type='text'>A couple of tips for RubyMine performance optimization</title><content type='html'>Set aside the IDE vs editor topic, one of the biggest complains about RubyMine is its performance. In a large project, without tweaking it a bit, RubyMine could be so sluggish that it becomes simply unusable. That's probably the #1 reason many RubyMine users ditch it and go back to simpler lightweight editor such as vim or TextMate. Well, there are actually a couple of things that can significantly impact the performance of RubyMine. With a little care, RubyMine can perform with an OK smoothness.&lt;br /&gt;1) By default, RubyMine's jvm max heap size bound is set as 512MB, it's probably be enough if you are running it on a 32bit JDK, but on a 64bit JDK, it might be necessary to bump it up to 1024MB, since the 64bit uses twice as much memory. This will reduce the number of GC collection. &lt;a href="http://www.williambharding.com/blog/uncategorized/setincrease-memory-available-in-rubymine/"&gt;Here is some guide on how.&lt;/a&gt;&lt;br /&gt;2) More importantly, RubyMine index everything in your project to support text search/refactoring etc. So if you have some huge files that also get changed very often, such as log, spec reports, etc, it could be very expensive to have RubyMine indexing them all the time. Exclude them from RubyMine project structure is probably a good idea.&lt;br /&gt;That's it. I think with a little love, RubyMine can still be a valid ruby development tool, especially for people who are used to JetBrian's IDEs.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-2372280160646152285?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/2372280160646152285/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=2372280160646152285' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/2372280160646152285'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/2372280160646152285'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2011/04/couple-of-tips-for-rubymine-performance.html' title='A couple of tips for RubyMine performance optimization'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-983665322899139409</id><published>2011-03-20T01:45:00.009-04:00</published><updated>2011-03-21T16:38:18.240-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='browsing'/><category scheme='http://www.blogger.com/atom/ns#' term='iPad'/><category scheme='http://www.blogger.com/atom/ns#' term='iPad2'/><category scheme='http://www.blogger.com/atom/ns#' term='internet'/><category scheme='http://www.blogger.com/atom/ns#' term='honeycomb'/><category scheme='http://www.blogger.com/atom/ns#' term='xoom'/><category scheme='http://www.blogger.com/atom/ns#' term='browser'/><title type='text'>iPad2 vs Xoom - Internet browsing</title><content type='html'>This article compares the Internet browsing experience on the two devices (Xoom running Honeycomb 3.0.1 vs iPad2 running iOS4.3), in the following 7 categories:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Operation&lt;/span&gt;&lt;br /&gt;  The quick control system on Honeycomb browser allows you to go back, forward, refresh, bookmark and other actions using your thumb when holding the tablet. This is not a default setting. User will need to enable it in the settings. Once enabled, the action bar will disappear and thus give you more real web page display. You can popup an action menu by swiping either of your thumbs from edge. The popup menus will display where your thumbnail swipes and is arranged around the thumb so that you can easily click an item. &lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://cdn.slashgear.com/wp-content/uploads/2011/02/xoom-browser-labs-quick-controls-3-AndroidCommunity-580x362.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 580px; height: 362px;" src="http://cdn.slashgear.com/wp-content/uploads/2011/02/xoom-browser-labs-quick-controls-3-AndroidCommunity-580x362.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;On iPad2, you move your hand to click on the buttons on the top.&lt;br /&gt;&lt;span style="font-style:italic;"&gt; &lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Winner: Xoom&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Multitab Browsing&lt;/span&gt; &lt;br /&gt;  On iPad2, to switch between tabs, you have to click the tabs button then chose a tab from the new tab grid view, this is a bit cumbersome especially if you are familiar with multi-tab browsing on a full pc/mac browser. On honeycomb, tabs are always displayed on the top just like they are on a desktop browser and switching tabs is a 1-click thing. &lt;br /&gt;Also, Xoom has 1GB memory while iPad2 has 512MB, whether this translates to more tabs supported on Xoom is yet subject to test. &lt;br /&gt;&lt;span style="font-style:italic;"&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Winner: Xoom&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Typing&lt;/span&gt;&lt;br /&gt;  The touch screen keyboard on iPad2 is designed for a phone (just like the whole OS) and thus not suitable for the bigger tablet layout. Typing on it is even slower than typing on iphone, basically you are forced to type a full size keyboard using one finger. On honeycomb, you can download the thumb keyboard input method from market (free). This soft keyboard layout the keys around the bottom left and right corner so that you can type using both thumbs while holding the tablet with both hand, pretty much like when you type on smart phone with both thumbs, and thus achieve the same typing speed. &lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://cdn3.jeftek.com/wp-content/uploads/2011/02/Xoom-ThumbKeyboard.png"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 580px; height: 362px;" src="http://cdn3.jeftek.com/wp-content/uploads/2011/02/Xoom-ThumbKeyboard.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Winner: Xoom&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Screen real estate&lt;/span&gt; &lt;br /&gt;  Xoom has a higher resolution 1280x800 comparing to ipad2's 1024x768. This means Xoom displays more content than iPad 2 when viewing the same page.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;&lt;span style="font-weight:bold;"&gt;Winner: Xoom&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Color Rendering&lt;/span&gt; &lt;br /&gt;  iPad2 is the clear winner in this category due to the superior IPS screen it's using while Xoom is using TFT which is common on laptops. Basically Xoom gives you the same color as normal laptop, while iPad2 gives you the color as on macs.  &lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;&lt;span style="font-weight:bold;"&gt;Winner: iPad2&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Flash&lt;/span&gt; &lt;br /&gt;  Xoom supports Flash while iPad2 doesn't. It means there a great number of websites that xoom can visit normally while iPad2 simply can't. According to &lt;a href="http://dev.opera.com/articles/view/mama-key-findings/"&gt;a survey done in 2008 &lt;/a&gt;, somewhere between 30% and 40% of all pages tested contained Flash files. Most popular websites already started to provide versions compatible to iPad, but it could be annoying when you bump into one that doesn't.&lt;br /&gt;&lt;span style="font-style:italic;"&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Winner: Xoom&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Speed&lt;/span&gt;&lt;br /&gt;   According to several tests, when both on the same wifi network, Honeycomb browser on Xoom is on par with if not slightly faster than safari on iPad2. However, when without Wifi availability, Xoom will be able to work on the LTE 4G network (after the coming free upgrade from Motorola) while iPad2 can only work on the slower 3G network.  &lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;&lt;span style="font-weight:bold;"&gt;For now: tied, Winner in the near future: Xoom &lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-983665322899139409?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/983665322899139409/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=983665322899139409' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/983665322899139409'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/983665322899139409'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2011/03/ipad2-vs-xoom-internet-browsing.html' title='iPad2 vs Xoom - Internet browsing'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-1693228292114479069</id><published>2011-03-19T23:01:00.005-04:00</published><updated>2011-03-20T00:26:19.736-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pair programming'/><category scheme='http://www.blogger.com/atom/ns#' term='readability'/><category scheme='http://www.blogger.com/atom/ns#' term='pair switch'/><title type='text'>Readability and pair programming</title><content type='html'>One of the issues regarding readability control is that readability is a subjective concept and thus difficult to rely on a general guideline plus devs' self governing to control. Some code review process has been designed to mitigate such risks and in my other post "&lt;a href="http://kailuowang.blogspot.com/2010/10/different-way-of-coding-explained.html"&gt;Another way of coding explained&lt;/a&gt;"  I also tried to establish some more objective standards for readability. The most effective way of controlling readability, however, is probably pair programming with regular pair switch while both devs in the pair always focus on readability.&lt;br /&gt;&lt;br /&gt;Pairing means constant code review, but for readability control purpose, this is not enough. By definition, readability cannot be reviewed by the dev that wrote the code, because obviously if you wrote the code, you don't really need to read it. You already know all the rational behind the code and everything just makes sense to you. This why regular pair switching is also critical for readability control in pair programming. &lt;br /&gt;By regular pair switch, I mean pair switch every 1-2 days so that most medium and plus size stories got implemented by more than 2 devs. Each switch means a little ramp up for the new dev that gets switched into the pair. The dev that remains on the pair needs to explain the story and all the existing code and design decisions with the new dev. This is a great opportunity to put the readability of the code under test. The new dev raises all readability issues he noticed when reading the code. The old dev does (or should do) the same thing when s/he finds some code more difficult to explain than others.&lt;br /&gt;&lt;br /&gt;So this is another reason for regular pair switching and pair programming in general.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-1693228292114479069?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/1693228292114479069/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=1693228292114479069' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/1693228292114479069'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/1693228292114479069'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2011/03/readability-and-pair-programming.html' title='Readability and pair programming'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-7416006840296560048</id><published>2011-03-19T11:14:00.004-04:00</published><updated>2011-03-19T11:53:02.663-04:00</updated><title type='text'>Setup a Rails 3 app on an EC2 quick start linux AMI</title><content type='html'>Just setup a Rails 3, Ruby 1.9.2 on a free ec2 micro instance (&lt;a href="http://aws.amazon.com/free"&gt;http://aws.amazon.com/free&lt;/a&gt;)&lt;br /&gt;Started from the quick Basic 32-bit Amazon Linux AMI 2011.02.1 Beta AMI, I setup the following stack in less than 2 hours.&lt;br /&gt;git&lt;br /&gt;ruby 1.9.2&lt;br /&gt;rubygem&lt;br /&gt;MySQL&lt;br /&gt;Apache&lt;br /&gt;Passenger&lt;br /&gt;And my Rails 3 app.&lt;br /&gt;&lt;br /&gt;The whole process is mostly smooth. All dependencies can be found on yum. &lt;br /&gt;It looks like that the free (for a year) micro EC2 instance is quite a valid solution for personal ruby projects.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-7416006840296560048?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/7416006840296560048/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=7416006840296560048' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7416006840296560048'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7416006840296560048'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2011/03/setup-rails-3-app-on-ec2-quick-start.html' title='Setup a Rails 3 app on an EC2 quick start linux AMI'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-5765688487863261802</id><published>2010-12-20T12:23:00.003-05:00</published><updated>2010-12-20T12:31:02.036-05:00</updated><title type='text'>Photography Assistant is open sourced</title><content type='html'>Here is the github address. &lt;br /&gt;https://github.com/kailuowang/LightMeter&lt;br /&gt;The idea10 project files are checked in. &lt;br /&gt;I am not sure if the quality of the code is good enough to be open source, I probably should've improved it further but I am a bit sick of writing Java and the painful process of running Android UI tests.&lt;br /&gt;Out of the features that I really want from this app, the only one left is the photography log. I may come back to implement this in the near future. If you want to contribute, please let me know so that I will provide more documentation (like how to build/run tests and so on)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-5765688487863261802?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/5765688487863261802/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=5765688487863261802' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5765688487863261802'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5765688487863261802'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/12/photography-assistant-is-open-source.html' title='Photography Assistant is open sourced'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-7994480502310499370</id><published>2010-12-19T00:32:00.005-05:00</published><updated>2010-12-20T12:32:05.725-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Photography'/><category scheme='http://www.blogger.com/atom/ns#' term='LightMeter'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><title type='text'>Photography Assistant 1.5 released</title><content type='html'>As a holiday gift to the 16000 users of my Android App Photography Assistant, version 1.5 is finally released. &lt;br /&gt;This release includes a useful new feature - select light value from scenarios. Now you can first select the category of you light scenario (namely outdoor natural light, out door artificial light and indoor), and then select your light scenario from the list of typical light scenarios. For example, you can first select the "Indoor" category and then the "Office and work areas" light scenario. &lt;br /&gt;There are also some minor bug fixes in this release. &lt;br /&gt;&lt;br /&gt;Happy holiday! Enjoy camera life!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-7994480502310499370?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/7994480502310499370/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=7994480502310499370' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7994480502310499370'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7994480502310499370'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/12/photography-assistant-15-released.html' title='Photography Assistant 1.5 released'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-9089429383646961966</id><published>2010-11-12T00:26:00.007-05:00</published><updated>2010-11-12T01:44:02.481-05:00</updated><title type='text'>Inheritance and class variable</title><content type='html'>Yesterday I had a great lunch &amp; learn presented by &lt;a href="http://brianguthrie.com"&gt;Brian Guthrie&lt;/a&gt; titled "&lt;a href="http://www.slideshare.net/btguthrie/advanced-ruby-idioms-so-clean-you-can-eat-off-of-them"&gt;Advanced Ruby Idioms So Clean You Can Eat Off Of Them&lt;/a&gt;"&lt;br /&gt;It was a great talk and we all learned something interesting about ruby. After the talk Brian and I had a quick discussion about one of the design problems solved in the talk. It is about the inheritance problem in the custom validation framework. That is the subclass (Captain) didn't inherit the validators from the super class(Pirate). &lt;br /&gt;Here is the code  &lt;br /&gt;&lt;br /&gt;&lt;fieldset style="white-space: pre;"&gt;&lt;br /&gt;&lt;legend&gt;Model classes&lt;/legend&gt;&lt;br /&gt;class Pirate &lt; BrianRecord::Base&lt;br /&gt;  validates_presence_of :parrot&lt;br /&gt;  attr_reader :parrot&lt;br /&gt;  def initialize(parrot=nil)&lt;br /&gt;    @parrot = parrot&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class Captain &lt; Pirate&lt;br /&gt;  validates_presence_of :peg_leg&lt;br /&gt;  attr_reader :peg_leg&lt;br /&gt;  def initialize(parrot, peg_leg)&lt;br /&gt;    @parrot, @peg_leg = parrot, peg_leg&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;br /&gt;&lt;fieldset style="white-space: pre;"&gt;&lt;br /&gt;&lt;legend&gt;Framework base class&lt;/legend&gt;&lt;br /&gt;module BrianRecord&lt;br /&gt;  class Base&lt;br /&gt;&lt;br /&gt;    class &lt;&lt; self&lt;br /&gt;       def validators&lt;br /&gt;          @validators ||= []&lt;br /&gt;       end&lt;br /&gt;&lt;br /&gt;       def validates_presence_of(attribute, opts={})&lt;br /&gt;          validators &lt;&lt; BrianRecord::PresenceOfValidator.new(attribute, opts)&lt;br /&gt;       end&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    def valid?&lt;br /&gt;        validators.each do |validator|&lt;br /&gt;           is_valid = validator.validate(self)&lt;br /&gt;           raise validator.message unless is_valid&lt;br /&gt;        end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  class PresenceOfValidator&lt;br /&gt;     def initialize(attribute, opts={})&lt;br /&gt;         @attribute = attribute&lt;br /&gt;     end&lt;br /&gt;&lt;br /&gt;     def message&lt;br /&gt;        "expected #{@attribute} to not be nil"&lt;br /&gt;     end&lt;br /&gt;&lt;br /&gt;     def validate(object)&lt;br /&gt;        return !object.send(@attribute).nil?&lt;br /&gt;     end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;br /&gt;The problem here is that the Captain class lost the validation on parrot attribute which means that you can do the following:&lt;br /&gt;&lt;fieldset style="white-space: pre;"&gt;&lt;br /&gt;&lt;legend&gt;failed scenario&lt;/legend&gt;&lt;br /&gt;Captain.new(nil, "wood").valid?  #=&gt;true, not as intended&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;The root cause here is that the @validator is only available to a certain class, both Captain and Pirate have their own @validators, so the validate method only iterate thru the @validator available to its own class. &lt;br /&gt;The quick fix Brian provided in the talk was to duplicate the superclass's @validators when initialized the @validators for the subclass, as the following&lt;br /&gt;&lt;fieldset style="white-space: pre;"&gt;&lt;br /&gt;&lt;legend&gt;Framework base class&lt;/legend&gt;&lt;br /&gt;module BrianRecord&lt;br /&gt;  class Base&lt;br /&gt;    class &lt;&lt; self&lt;br /&gt;       def validators&lt;br /&gt;          @validators ||= if(superclass.respond_to(:validators)&lt;br /&gt;               superclass.validators.dup &lt;br /&gt;           else [] end&lt;br /&gt;       end&lt;br /&gt;    ...&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;This is fine and concise but with only one small glitch, if an validator is added to the superclass after the subclass is loaded, that validator will not be available to the subclass. I brought up another possible solution and we were not sure if it will work. So I didn't a bit coding practice and it looks that the following code also works. &lt;br /&gt;&lt;br /&gt;&lt;fieldset style="white-space: pre;"&gt;&lt;br /&gt;&lt;legend&gt;Framework base class&lt;/legend&gt;&lt;br /&gt;module BrianRecord&lt;br /&gt;  class Base&lt;br /&gt;&lt;br /&gt;    class &lt;&lt; self&lt;br /&gt;     &lt;br /&gt;&lt;br /&gt;      def validates_presence_of(attribute, opts={})&lt;br /&gt;        validators &lt;&lt; BrianRecord::PresenceOfValidator.new(attribute, opts)&lt;br /&gt;      end&lt;br /&gt;&lt;br /&gt;      def validate obj&lt;br /&gt;         superclass.validate obj if superclass.respond_to?(:validate)&lt;br /&gt;         validators.each do |validator|&lt;br /&gt;            is_valid = validator.validate(obj)&lt;br /&gt;            raise validator.message unless is_valid&lt;br /&gt;         end&lt;br /&gt;      end&lt;br /&gt;     &lt;br /&gt;      private&lt;br /&gt;      def validators&lt;br /&gt;        @validators ||= []&lt;br /&gt;      end&lt;br /&gt;&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    def valid?&lt;br /&gt;      self.class.validate self&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;In this solution, I move the validate logic to the class, and recursively calls the superclass' validate method if its available. This removed the duplication of the @validators and thus avoid the problem where dynamically added validators will be missing in the subclass. Note here that now that we have a public "validate" class method instead of the public "validators" accessor, which is private now. I would argue that exposing the validate method is better than exposing the validators.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-9089429383646961966?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/9089429383646961966/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=9089429383646961966' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/9089429383646961966'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/9089429383646961966'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/11/inheritance.html' title='Inheritance and class variable'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-7338802927031930843</id><published>2010-10-12T23:24:00.047-04:00</published><updated>2011-05-03T23:08:26.713-04:00</updated><title type='text'>A different way of coding explained.</title><content type='html'>&lt;i&gt;Disclaimer: I will probably keep editing this article and thus the content is subject to change.&lt;/i&gt; &lt;br /&gt;&lt;br /&gt;Recently I developed my new pet theory - a different (different from traditional TDD/BDD) way of coding that focuses on readability can be feasible and beneficial. &lt;br /&gt;&lt;br /&gt;I am fully aware that this theory might be flawed, but I feel the need to document it here because I think although the software industry has made huge progress in software development processes (TDD, BDD, xP, etc), we still have a long way to go. We follow those processes/practices, but we still produce code that later becomes pain to ourselves. We need to keep the thinking of new coding strategies/processes going.     &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;In this article I am going to explain this pet theory in three sections. &lt;br /&gt;&lt;a href="#sectionI" &gt;Section 1&lt;/a&gt; is to discuss why readability is definable and can be set as #1 priority for coding. &lt;br /&gt;&lt;a href="#sectionII" &gt;Section 2&lt;/a&gt; is about why readability should be the #1 priority in terms of code quality. &lt;br /&gt;&lt;a href="#sectionIII" &gt;Section 3&lt;/a&gt; is to introduce a difference way of coding (I call it code in units) that was built upon of the principle of extreme readability.&lt;br /&gt;&lt;br /&gt;&lt;a id="sectionI"&gt;&lt;/a&gt;&lt;br /&gt;&lt;h3&gt; Section I - what is readability? what is extreme readability?&lt;/h3&gt;&lt;br /&gt;From a couple of recent large projects, I refreshed my appreciation of how import to have a readable code base. After some further thoughts and discussions with other ThoughtWorkers on this particular topic, I feel that readability should be defined clearly so that it can be used a main principle. &lt;br /&gt;&lt;br /&gt;The first reaction many developers had when I discussed readability with them is that readability is subjective and hard to define.  &lt;br /&gt;To some degree I agree with this observation but I believe this problem can be tackled by taking smaller steps. First we can define some coordinates for readability, then we define the extremes on those coordinates. With such definitions, readability can be more objective to the team than subjective to each person.&lt;br /&gt;&lt;br /&gt;It makes sense to me that we have the two readability coordinates defined as the following:&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Coordinate I. Identifying logic for given code.&lt;/span&gt; Specifically, for a developer that is new to the project but reasonably familiar with the technology stack and the business domain, how hard will it be for him to understand the intention of a given unit of code (method or class) to the extent that he knows how to change that code to incorporate new attention.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Coordinate II. Identifying code for given logic. &lt;/span&gt; Specifically, for the developer described above, how hard will it be for him to locate the implementation code for a certain unit of business logic. &lt;br /&gt;&lt;br /&gt;These two coordinates are defined based on the two main purposes for us to read the code on a daily basis. &lt;br /&gt;I am not sure if it's possible to define the sad extreme for these coordinates. One way might be that it's so unreadable that no programmer can achieve these purposes before he quits. &lt;br /&gt;&lt;br /&gt;Fortunately it's the happy extremes that interest us the most:&lt;br /&gt;For Coordinate I, the happy extreme is that the developer can understand the intention of the code by browsing from the beginning to the end of that unit of code only once. &lt;br /&gt;For coordinate #2, the happy extreme is that he should be able to locate that implementation through a single path of search, in another sentence, he should neither be misled to a wrong path or find out that the unit of logic is implemented in multiple places (compositely or repeatedly or both). &lt;br /&gt;&lt;br /&gt;To be more specific, I would list here some implications of these two extremes:&lt;br /&gt;For happy extreme on coordinate #1, the implication of being able to understand the code by reading it once are: &lt;br /&gt;&lt;ul&gt;&lt;li&gt; the unit of code can't be large otherwise it will be hard to keep track of what's going on when you read it the first time. For a method, less than 3 statements will be easy for reader to understand in a glance, while 5 will be probably be a stretch. For a class, there should be no more than a handful of public methods so that the intention of the class can't be easily interpreted.      &lt;br /&gt;&lt;/li&gt;&lt;li&gt;there shall be no disruptive logic in this unit of code, it's difficult for reader to switch context when reading, which means that code should be cohesive.  &lt;br /&gt;&lt;/li&gt;&lt;li&gt; there should be no confusing naming (variables, parameters, methods or classes). Ideally name should reflect exactly the intention of the variable. Sometimes there is a tendency to name something more generic than it should be.&lt;br /&gt;&lt;/li&gt;&lt;li&gt; the developer should not need to refer to document or tests to understand the code&lt;br /&gt;&lt;/li&gt;&lt;li&gt; tests should be understandable without description (or long method name) &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;For happy extreme #2, being able to locate the implementation through a single search path have the following implications: &lt;br /&gt;&lt;ul&gt;&lt;li&gt;for a certain unit of business logic, it should always be implemented only once in one place.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;responsibilities should be divided clearly so that sub-logic can be easily located.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;it should be avoided that some knowledge of project specific convention is required to locate the code. &lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;If you are a believer of Extreme Programming like me, you might agree with me that we can pursue extreme readability. With the two coordinates are defined, it is rather straight forward, simply always keep asking yourself those two questions when writing the code to implement some logic:  how easy will it be for other developer to find my code and how easy for him to understand my code. Make your development/design decisions on top of them. &lt;br /&gt;&lt;a id="sectionII"&gt;&lt;/a&gt;&lt;br /&gt;&lt;h3&gt; Section II - why extreme readability can be a principle for coding &lt;/h3&gt;&lt;br /&gt;With extreme readability defined, I think it's possible to simplify the principles of coding as the following &lt;br /&gt;&lt;ul&gt;&lt;br /&gt; &lt;li&gt; Principle #1 - write code that works, which is so obvious that I probably shouldn't even include it in the following discussion &lt;/li&gt;&lt;br /&gt; &lt;li&gt; Principle #2 - write code that is easy to read &lt;/li&gt; &lt;br /&gt; &lt;li&gt; Principle #3 - write code that is tested or at least testable &lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;All other software principles, such as SOLID, could be forgotten (and often they are), but as long as these 3 easy principles are followed, the project should be sound and life should be good for every developers in the team. &lt;br /&gt;There is plenty of discussion why #3 principle is important, here is why I think the #2 readability principle should have a much higher priority than other coding/design principles:  &lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt; I found that following some of the common design principles blindly could lead to over engineer and thus makes the development less agile. but certainly others might have different opinion which brings the second point.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;  The readability principle is relatively easier to be agreed upon and followed than a bunch of design principles that often times very difficult for a big team to consistently follow simply because they are not unanimously agreed upon. And if a principle can't be consistently followed in a project, the majority of its value is often lost.  &lt;/li&gt;&lt;br /&gt;&lt;li&gt;  Many of the design principles can be deemed as consequential to the readability principle. In another sentence, they can be driven by following the readability principle. For example, the "Single responsibility principle" could be a natural consequence if we organize code cohesively so they are easy to read. There is more explanation on this in section I. Even the principle of always automate can be deduced from the readability principle - manual processes, when documented, have a lot more readability issues than automated process due to pretty much the same reasons why code comment has more readability issues than readable code.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;  Readability has more importance. Many deem tests as the #1 priority, but readability could be even more important than tests. I saw plenty of unreadable code that are fully tested but you still don't know how to change them. On the other hand, I rarely found any real readable but untested code that is hard to add test for. &lt;/li&gt;&lt;br /&gt;&lt;li&gt;  Readability always has value. A significant number of the design/development principles were developed to promote extensibility and thus their value can only materialize when the code is extended, not to mention that unnecessary extensibility is not only a waste but also can be a burden sometimes. On the other hand readability always have value, even when you are about to delete the code, you need to read it to make sure you delete the right one. In other words, focusing on readability is more agile than pursuing design principles that may or may not provide value.&lt;/li&gt; &lt;br /&gt;&lt;/ul&gt;&lt;br /&gt; &lt;br /&gt;&lt;a id="sectionIII"&gt;&lt;/a&gt;&lt;br /&gt;&lt;h3&gt; Section III - introduce code by units &lt;/h3&gt;&lt;br /&gt;Now lets introduce a strategy, a new way of coding to pursue extreme readability defined in section I and follow the 3 easy principles in section II.&lt;br /&gt;&lt;br /&gt;In one sentence, developers should always focus in a small unit of code (by "small unit" I mean 2-3 statements, no more than 5 ). In TDD, it means that developers are on a constant and quick rhythm of writing a small unit of test and then a small unit of implementation.&lt;br /&gt;&lt;br /&gt;Here is simple example, presume that we are developing a website in which we need to render links to pdfs on external websites. We have the urls of the links but we are not sure the availability of the pdfs on those websites. Some urls will redirect visitor to a html page saying that the pdf is not ready yet (but it maybe in the future). Other urls will simply return a http error. We need a checker to check this so that we can avoid rendering links that do not work.&lt;br /&gt;&lt;br /&gt;First let's see how this would be done in a traditional TDD fashion:&lt;br /&gt;Step 1, first you do some research and found out that you can use the Net::HTTP to get the http response of the url, with the sample code, you write the following test&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt; describe UrlChecker do&lt;br /&gt;    describe "#check_pdf" do&lt;br /&gt;       it "return true if url responses successfully and content is pdf"do&lt;br /&gt;         Net::HTTP.stub(:start).with("sample.com", 80).&lt;br /&gt;           and_return(mock(:res, :code =&gt; "200", :content_type =&gt; "application/pdf"))&lt;br /&gt;         UrlChecker.new.check_pdf("http://sample.com/some.pdf").should be_true&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt; end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;Note that you have to do this Net::HTTP library research before you can write the first test. &lt;br /&gt;Then you write the following code to have it pass&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt; class UrlChecker&lt;br /&gt;   def check_pdf pdf_url&lt;br /&gt;      url = URI.parse(pdf_url)&lt;br /&gt;      res = Net::HTTP.start(url.host, url.port) {|http|&lt;br /&gt;         http.get(url.path)&lt;br /&gt;      }&lt;br /&gt;      true&lt;br /&gt;    end&lt;br /&gt; end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;Then you add a failing test&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;     it "return false if url responses error"do&lt;br /&gt;       Net::HTTP.stub(:start).with("sample.com", 80).&lt;br /&gt;         and_return(mock(:res, :code =&gt; "500"))&lt;br /&gt;       UrlChecker.new.check_pdf("http://sample.com/some.pdf").should be_false&lt;br /&gt;     end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;To have it pass simply replace the last return true statement in method with the following&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt; res.code == "200"&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;Then another failing test to make sure you the content of the http response is a pdf file&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;     it "return false if url responses successfully but content is not pdf"do&lt;br /&gt;       Net::HTTP.stub(:start).with("sample.com", 80).&lt;br /&gt;         and_return(mock(:res, :code =&gt; "200", :content_type =&gt; "another content type"))&lt;br /&gt;       UrlChecker.new.check_pdf("http://sample.com/some.pdf").should be_false&lt;br /&gt;     end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;This time replace the last return statement as the following and the implementation is pretty much done&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;   res.code == "200" &amp;amp;&amp;amp; res.content_type  == 'application/pdf'&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;br /&gt;Now the class and test looks&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;url_checker.rb&lt;br /&gt;&lt;br /&gt; class UrlChecker&lt;br /&gt;   def check_pdf pdf_url&lt;br /&gt;     url = URI.parse(pdf_url)&lt;br /&gt;     res = Net::HTTP.start(url.host, url.port) {|http|&lt;br /&gt;       http.get(url.path)&lt;br /&gt;     }&lt;br /&gt;     res.code == "200" &amp;amp;&amp;amp; res.content_type  == 'application/pdf'&lt;br /&gt;   end&lt;br /&gt; end&lt;br /&gt;&lt;br /&gt;url_checker_spec.rb&lt;br /&gt;&lt;br /&gt; describe UrlChecker do&lt;br /&gt;   describe "#check_pdf" do&lt;br /&gt;     it "return false if url responses error"do&lt;br /&gt;       Net::HTTP.stub(:start).with("sample.com", 80).&lt;br /&gt;         and_return(mock(:res, :code =&gt; "500"))&lt;br /&gt;       UrlChecker.new.check_pdf("http://sample.com/some.pdf").should be_false&lt;br /&gt;     end&lt;br /&gt;&lt;br /&gt;     it "return false if url responses successfully but content is not pdf"do&lt;br /&gt;       Net::HTTP.stub(:start).with("sample.com", 80).&lt;br /&gt;         and_return(mock(:res, :code =&gt; "200", :content_type =&gt; "another content type"))&lt;br /&gt;       UrlChecker.new.check_pdf("http://sample.com/some.pdf").should be_false&lt;br /&gt;     end&lt;br /&gt;&lt;br /&gt;     it "return true if url responses successfully and content is pdf"do&lt;br /&gt;       Net::HTTP.stub(:start).with("sample.com", 80).&lt;br /&gt;         and_return(mock(:res, :code =&gt; "200", :content_type =&gt; "application/pdf"))&lt;br /&gt;       UrlChecker.new.check_pdf("http://sample.com/some.pdf").should be_true&lt;br /&gt;     end&lt;br /&gt;   end&lt;br /&gt; end&lt;br /&gt;&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;This code looks not bad right? Well, in normal development, I would probably leave them along, but if our goal is to achieve extreme readability there are a couple of issues here:&lt;br /&gt;First &lt;fieldset style="font-style:italic;"&gt;"Net::HTTP.stub(:start).with('sample.com', 80)"&lt;/fieldset&gt; is actually testing the url parse and it's repeated 3 times. This makes the tests harder to read because of irrelevant information so maybe this should tested in a separate test.&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;  it "should use the correct host name and port to request for the pdf" do&lt;br /&gt;     Net::HTTP.stub(:start).with("sample.com", 80).&lt;br /&gt;             and_return(mock(:res, :code =&gt; :some_code, :content_type =&gt; :some_content_type))&lt;br /&gt;     UrlChecker.new.check_pdf("http://sample.com/some.pdf")&lt;br /&gt;   end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;Then you can remove the ".with("sample.com", 80)" from the other tests, which helps the readability, for example:&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;   it "return true if url responses successfully and content is pdf" do&lt;br /&gt;     Net::HTTP.stub(:start).&lt;br /&gt;  and_return(mock(:res, :code =&gt; "200", :content_type =&gt; "application/pdf"))&lt;br /&gt;     UrlChecker.new.check_pdf("http://sample.com/some.pdf").should be_true&lt;br /&gt;   end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;As you can see the new test helped readability, but it itself has some readability issue - it had to mock the response even when it's not tested at all.&lt;br /&gt;&lt;br /&gt;Second, it didn't really test that you should use http GET to test the url. Actually these tests will still pass if you didn't write http.get(url.path) in the HTTP start block. That is definitely a fail. You probably need another test and break the two principles mentioned above.&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;     it "use http GET to request the pdf" do&lt;br /&gt;       http = mock(:http)&lt;br /&gt;       http.should_receive(:get).with("/a.pdf")&lt;br /&gt;       Net::HTTP.stub(:start).&lt;br /&gt;         and_yield(http).&lt;br /&gt;         and_return(mock(:res, :code =&gt; :some_code, :content_type =&gt; :some_content_type))&lt;br /&gt;       UrlChecker.new.check_pdf("http://sample.com/a.pdf")&lt;br /&gt;     end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;Again in this test, you actually don't care about the response because it's already tested in other tests, but you still have to mock it.&lt;br /&gt;&lt;br /&gt;Now you have 6 specs with some minor readability issues other than the two mentioned above such as 1) :code =&gt; "200" is duplicated twice, you will relie on the test description to understand that it means that the response is successful. You have the same problem in the implementation. 2) I would argue that you will need some knowledge of HTTP protocol to quickly understand the intention.&lt;br /&gt;&lt;br /&gt;It's quite a journey to achieve this isn't it? You will have to review the code and test carefully to identify the problem and then loose some principles to reach this sub-optimal point.&lt;br /&gt;&lt;br /&gt;Now let's see how we would do this in very different fashion, in which we only think in very small steps and code in very small units. And we only care about readability during coding. Here, the first small unit is that this check method should request for the url and return true if a pdf is responded.  As for how to request and how to request and check if it's a pdf we don't care for now.&lt;br /&gt;&lt;br /&gt;Here is the first test&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt; describe PdfChecker do&lt;br /&gt;   before do&lt;br /&gt;     @checker = PdfChecker.new&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;   it "#check_pdf should request for the pdf and check if response is a pdf" do&lt;br /&gt;     @checker.should_receive(:request).with(:pdf_url).and_return(:response)&lt;br /&gt;     @checker.should_receive(:pdf?).with(:response).and_return(:is_pdf?)&lt;br /&gt;     @checker.check(:pdf_url).should == :is_pdf?&lt;br /&gt;   end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;Implementation&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;  def check pdf_url&lt;br /&gt;     pdf?(request(pdf_url))&lt;br /&gt;  end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;br /&gt;simple enough, now let's worry about how to do the request, this is the time we do a bit google to find out about the Net::HTTP library. Here the test:&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;it "#request should use Net::HTTP to get the pdf and return response" do&lt;br /&gt;   http = mock(:http)&lt;br /&gt;   http.should_receive(:get).with("/a.pdf")&lt;br /&gt;   Net::HTTP.should_receive(:start).with("sample.com", 80).&lt;br /&gt;      and_yield(http).and_return(:response)&lt;br /&gt;   @checker.request("http://sample.com/a.pdf").should == :response&lt;br /&gt; end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;implementation&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;  def request url&lt;br /&gt;    url = URI.parse(url)&lt;br /&gt;    Net::HTTP.start(url.host, url.port) {|http|&lt;br /&gt;       http.get(url.path)&lt;br /&gt;    }&lt;br /&gt;  end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;Then we can worry about how to tell if the response is a pdf. First the response should be successful and then it's content should be a pdf. Again we don't worry about how to tell successful or content type yet.&lt;br /&gt;&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;it "#pdf? should only return true if response is success and content is pdf" do&lt;br /&gt;  [true, false].each do |success|&lt;br /&gt;    [true, false].each do |content_pdf|&lt;br /&gt;      checker = PdfChecker.new&lt;br /&gt;      checker.should_receive(:success?).with(:response).and_return(success)&lt;br /&gt;      checker.should_receive(:content_pdf?).with(:response).and_return(content_pdf) if success&lt;br /&gt;      checker.pdf?(:response).should == (success &amp;&amp; content_pdf)&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;  def pdf? response&lt;br /&gt;    success?(response) &amp;&amp; content_pdf?(response)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;br /&gt;Then another small step, how to tell if the response is successful&lt;br /&gt;&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;  it "#success should return true only when response.code is 200, otherwise false" do&lt;br /&gt;    @checker.success?(mock(:response, :code =&gt; "200")).should be_true&lt;br /&gt;    @checker.success?(mock(:response, :code =&gt; :anything_other_than200)).should be_false&lt;br /&gt;  end&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;  def success? response&lt;br /&gt;    response.code == "200"&lt;br /&gt;  end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;br /&gt;Finally, take care of how to tell if the content is pdf&lt;br /&gt;&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;  it "#content_pdf? should return true only when response.content_type is 'application/pdf'" do&lt;br /&gt;    @checker.content_pdf?(mock(:response, :content_type =&gt; "application/pdf")).&lt;br /&gt;   should be_true&lt;br /&gt;    @checker.content_pdf?(mock(:response, :content_type =&gt; :anything_else)).&lt;br /&gt;   should be_false&lt;br /&gt;  end&lt;br /&gt;...&lt;br /&gt;  def content_pdf? response&lt;br /&gt;    response.content_type == "application/pdf"&lt;br /&gt;  end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;br /&gt;Now the class and test look like the following&lt;br /&gt;&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;&lt;br /&gt;   class PdfChecker&lt;br /&gt;&lt;br /&gt;      def check pdf_url&lt;br /&gt;        pdf?(request(pdf_url))&lt;br /&gt;      end&lt;br /&gt;&lt;br /&gt;      private&lt;br /&gt;      def request url&lt;br /&gt;        url = URI.parse(url)&lt;br /&gt;        Net::HTTP.start(url.host, url.port) {|http|&lt;br /&gt;          http.get(url.path)&lt;br /&gt;        }&lt;br /&gt;      end&lt;br /&gt;&lt;br /&gt;      def pdf? response&lt;br /&gt;        success?(response) &amp;&amp; content_pdf?(response)&lt;br /&gt;      end&lt;br /&gt;&lt;br /&gt;      def success? response&lt;br /&gt;        response.code == "200"&lt;br /&gt;      end&lt;br /&gt;&lt;br /&gt;      def content_pdf? response&lt;br /&gt;        response.content_type == "application/pdf"&lt;br /&gt;      end&lt;br /&gt;&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;before do&lt;br /&gt;  @checker = PdfChecker.new&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;describe PdfChecker do&lt;br /&gt;  it "#check_pdf should request for the pdf and check if response is a pdf" do&lt;br /&gt;    @checker.should_receive(:request).with(:pdf_url).and_return(:response)&lt;br /&gt;    @checker.should_receive(:pdf?).with(:response).and_return(:is_pdf?)&lt;br /&gt;    @checker.check(:pdf_url).should == :is_pdf?&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;describe_internally PdfChecker do&lt;br /&gt;  it "#request should use Net::HTTP to get the pdf and return response" do&lt;br /&gt;    http = mock(:http)&lt;br /&gt;    http.should_receive(:get).with("/a.pdf")&lt;br /&gt;    Net::HTTP.should_receive(:start).with("sample.com", 80).&lt;br /&gt;      and_yield(http).and_return(:response)&lt;br /&gt;    @checker.request("http://sample.com/a.pdf").should == :response&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  it "#pdf? should only return true if response is success and content is pdf" do&lt;br /&gt;    [true, false].each do |success|&lt;br /&gt;      [true, false].each do |content_pdf|&lt;br /&gt;        checker = PdfChecker.new&lt;br /&gt;        checker.should_receive(:success?).with(:response).and_return(success)&lt;br /&gt;        checker.should_receive(:content_pdf?).with(:response).and_return(content_pdf) if success&lt;br /&gt;        checker.pdf?(:response).should == (success &amp;&amp; content_pdf)&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  it "#success should return true only when response.code is 200, otherwise false" do&lt;br /&gt;    @checker.success?(mock(:response, :code =&gt; "200")).should be_true&lt;br /&gt;    @checker.success?(mock(:response, :code =&gt; :anything_other_than200)).should be_false&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  it "#content_pdf? should return true only if content_type is 'application/pdf'" do&lt;br /&gt;    @checker.content_pdf?(mock(:response, :content_type =&gt; "application/pdf")).&lt;br /&gt;      should be_true&lt;br /&gt;    @checker.content_pdf?(mock(:response, :content_type =&gt; :anything_elsef)).&lt;br /&gt;      should be_false&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;Note that I used &lt;a href="http://kailuowang.blogspot.com/2010/08/testing-private-methods-in-rspec.html"&gt;a small trick &lt;/a&gt; here to test private methods, that detail is hidden here.&lt;br /&gt;&lt;br /&gt;In this version, our code has better readability. The intention of the class can be quickly understand by reading the public methods. Implementation detail can be easily located in the private methods. Each private method is very easy to read. On the test side, there is no duplicated tests (no logic is tested more than once) or duplicated mock (no logic is mocked more than once). Every test can be easily understood without reading the description. We can say that this code satisfies the definitions of extreme readability. Interestingly, this kind of unit thinking resulted in a code style that is more functional than imperative. &lt;br /&gt;&lt;br /&gt;The specs here are separated into two groups - the normal describe PdfChecker block tests the public interface. However, in our case, instead of testing the behavior, it's more like a description of what the code does than a test. That's actually due to the fact that now the code body of the public method is merely delegation to internal helper methods. So maybe we can replace this block with some more end to end functional tests ( which happen to be difficult in this case).  &lt;br /&gt;&lt;br /&gt; On the other hand, at least too very common practices were not followed: 1) do not mock the class you are testing and 2) do not test private methods. Also there is minimum level of integration tests. In order to do this thinking in small step and then coding in small unit fashion, we dramatically changed our way of testing. We need to examine if this new controversial way of testing is a price too high to pay. From now on, we are going to call this style of coding "code in units"&lt;br /&gt;&lt;br /&gt;We can try do some preliminary examination by trying to add some extra features to the code and then some hypothetical bug fixing.&lt;br /&gt;1, adding proxy support, 2, adding cache function, 3, changing HTTP Library.&lt;br /&gt;&lt;br /&gt;&lt;h5&gt; Round 1, let's add proxy support to the class so that it can also work in an environment where proxy is needed to reach external websites.&lt;/h5&gt;&lt;br /&gt;Let's assume that we are going to store the proxy settings as instance variables when instantiating the checker and use them when doing the checker.&lt;br /&gt;&lt;br /&gt;In the traditional coding, you will add another test like the following&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;    it "use proxy to do HTTP request" do&lt;br /&gt;      http = mock(:http)&lt;br /&gt;      Net::HTTP.should_receive(:Proxy).with('proxy.host', 8080).and_return http&lt;br /&gt;      http.stub(:start).and_return(mock(:res, :code =&gt; nil))&lt;br /&gt;      UrlChecker.new('proxy.host', 8080).check_pdf("http://sample.com/a.pdf")&lt;br /&gt;    end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;It's mostly okay except that you are forced to mock the http response although you are not testing it. It will take reader a bit time to figure out that :code =&gt; nil is irrelevant to this test.&lt;br /&gt;&lt;br /&gt;Then you modify the class code&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;     def initialize(proxy_addr = nil, proxy_port = nil)&lt;br /&gt;        @proxy_addr, @proxy_port = proxy_addr, proxy_port&lt;br /&gt;     end&lt;br /&gt;&lt;br /&gt;    def check_pdf pdf_url&lt;br /&gt;       url = URI.parse(pdf_url)&lt;br /&gt;       res = Net::HTTP::Proxy(@proxy_addr, @proxy_port).&lt;br /&gt;          start(url.host, url.port) { |http|&lt;br /&gt;             http.get(url.path)&lt;br /&gt;        }&lt;br /&gt;        res.code == "200" &amp;&amp; res.content_type == 'application/pdf'&lt;br /&gt;     end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;All tests pass again. It might be a bit surprising for people who are not familiar with the net/http library, because in the code it's using Net::HTTP::Proxy.start while all the other tests are mocking Net::HTTP.start. It is passing because when proxy_addr is nil, Net::HTTP::Proxy(proxy_addr, proxy_port) simply returns Net::HTTP. Surprise is not a good thing when talking about readability. It also means now our tests actually depend on a library's implementation detail although from the tests, it looks like that we are mocking it. And from those tests it looks like the class is using Net::HTTP directly. To solve this problem, we will need to change all of our tests to mock Net::HTTP::Proxy instead. That is to replace "Net::HTTP.stub(:start)..." with the following&lt;br /&gt;&lt;br /&gt; &lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;      http = mock(:http)&lt;br /&gt;      Net::HTTP.stub(:Proxy).and_return http&lt;br /&gt;      http.stub(:start)...&lt;br /&gt;  &lt;/fieldset&gt;&lt;br /&gt;&lt;br /&gt;What about in code in units fashion? We change the existing test on the request method to the following&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;   it "#request should use Net::HTTP::Proxy to get the pdf and return response" do&lt;br /&gt;      connection = mock(:http)&lt;br /&gt;      http = mock(:http)&lt;br /&gt;      Net::HTTP.should_receive(:Proxy).with(:proxy_addr, :proxy_port).and_return http&lt;br /&gt;      http.should_receive(:start).with("sample.com", 80).&lt;br /&gt;         and_yield(connection).and_return(:response)&lt;br /&gt;      connection.should_receive(:get).with("/a.pdf")&lt;br /&gt;      PdfChecker.new(:proxy_addr, :proxy_port).&lt;br /&gt;        request("http://sample.com/a.pdf").should == :response&lt;br /&gt;   end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;br /&gt;Then we add the same constructor in the pdf checker and change the request method as follows&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;      def request url&lt;br /&gt;        url = URI.parse(url)&lt;br /&gt;        Net::HTTP::Proxy(@proxy_addr, @proxy_port).start(url.host, url.port) {|http|&lt;br /&gt;          http.get(url.path)&lt;br /&gt;        }&lt;br /&gt;      end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;That's it. There is no impact at all to other tests. All interactions between our class and the net/http library are captured in one private method and one test on that method.&lt;br /&gt;&lt;br /&gt;&lt;h5&gt;Round 2, let's add a cache function so that the result of the check can be cached to prevent excessive http requests. &lt;/h5&gt;&lt;br /&gt;Let's assume that once a pdf becomes available it will always be available. So we want to provide another public method which also returns the availability of the pdf url. This method, however, will do the http request checking if the url is not http checked before or it's checked before but was not available at that time. If it is checked before and it was available, the result (true) will be returned without any http request.&lt;br /&gt; &lt;br /&gt;First, traditional code style, we construct the tests based on the spec mentioned above. &lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt; describe "#available?" do&lt;br /&gt;   it "should only call http request once if an url is available and checked before" do&lt;br /&gt;     http = mock(:http)&lt;br /&gt;     Net::HTTP.stub(:Proxy).and_return http&lt;br /&gt;     http.should_receive(:start).&lt;br /&gt;             and_return(mock(:res, :code =&gt; "200", :content_type =&gt; "application/pdf"))&lt;br /&gt;     checker = UrlChecker.new&lt;br /&gt;     checker.available?("http://sample.com/some.pdf").should == true&lt;br /&gt;     checker.available?("http://sample.com/some.pdf").should == true&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;   it "should recheck by http request when the url was checked before and not available" do&lt;br /&gt;     http = mock(:http)&lt;br /&gt;     Net::HTTP.stub(:Proxy).and_return http&lt;br /&gt;     http.should_receive(:start).&lt;br /&gt;             and_return(mock(:res, :code =&gt; "500"))&lt;br /&gt;     checker = UrlChecker.new&lt;br /&gt;     checker.available?("http://sample.com/some.pdf").should == false&lt;br /&gt;     http.should_receive(:start).&lt;br /&gt;                 and_return(mock(:res, :code =&gt; "200", :content_type =&gt; "application/pdf"))&lt;br /&gt;     checker.available?("http://sample.com/some.pdf").should == true&lt;br /&gt;   end&lt;br /&gt; end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;The reason I wrote two tests at the same time rather than having the first test pass and then write the second one is that after I wrote the first one, I realized that it is easier to implement it in a way so that both tests pass. Here is the implementation:&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt;  def available? pdf_url&lt;br /&gt;    @cache ||= {}&lt;br /&gt;    @cache[pdf_url] ||= check_pdf(pdf_url)&lt;br /&gt;  end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;All tests pass now. Three issues here. 1) I was forced to mock the http response code and content_type again. 2) HTTP protocol knowledge is required to understand the intention of the tests, you need to instantly translate the "code =&gt; '500'" to an available pdf and ":code =&gt; '200', :content_type =&gt; 'application/pdf' to an unavailable pdf. 3) In the second test, the intention is to test if the available method returns the result of the second check. However, we only tested the scenario when the pdf became available in the second check, we should've also checked the scenario where it's still unavailable, but the benefit of this such might be canceled out by the duplicated code introduced.   &lt;br /&gt;&lt;br /&gt;Alright, now let's try do this in the code in units fashion. The way to proceed this is different from the traditional way. First you read the code before writing the test. The only public method is check&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt; def check pdf_url&lt;br /&gt;       pdf?(request(pdf_url))&lt;br /&gt;     end &lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;It's extremely easy to see that it's doing a request. In the code by units testing fashion, the tests can be written to test if the new method calls this check method correctly. &lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt; it "#available? should only check once if it was available" do&lt;br /&gt;   @checker.should_receive(:check).once.with(:url).and_return true&lt;br /&gt;   @checker.available?(:url).should == true&lt;br /&gt;   @checker.available?(:url).should == true&lt;br /&gt; end&lt;br /&gt;&lt;br /&gt; it "#available? should re-check if it wasn't available during last check" do&lt;br /&gt;   @checker.should_receive(:check).with(:url).and_return false&lt;br /&gt;   @checker.available?(:url).should == false&lt;br /&gt;   @checker.should_receive(:check).with(:url).and_return :new_result&lt;br /&gt;   @checker.available?(:url).should == :new_result&lt;br /&gt; end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;Note how easy these two tests are to read comparing to the ones written in the traditional fashion. They avoided all the issues mentioned above in the traditional ones. In the second test, it also effortlessly tested that the available method should return the result from the latest check. Of course, it has its own issue, it didn't really explicitly test that the http request was not sent when not necessary. But I can hardly think of a scenario where someone change some code so that these tests still pass but fail on the intention. Maybe someone change the available? method so that it does its own http request checking too. Provide that the code is very readable and the idea is that other developers should read the implementation code (not just the tests), such chance should be low.     &lt;br /&gt;&lt;br /&gt;The implementation is the same as traditional way&lt;br /&gt;&lt;fieldset style="white-space: pre;font-size:90%;"&gt;&lt;br /&gt; def available? pdf_url&lt;br /&gt;   @cache ||= {}&lt;br /&gt;   @cache[pdf_url] ||= check(pdf_url)&lt;br /&gt; end&lt;br /&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;h5&gt; Round 3, let's pretend that the HTTP library we are using was changed, which introduced a bug which we need to fix&lt;/h5&gt; &lt;br /&gt;Assume that we upgrade to a new HTTP library but we didn't know that the response code attribute in HttpResponse is changed from code to resp_code. &lt;br /&gt;&lt;br /&gt;First of tests in both coding fashion will fail to discover this because they both mocked the response. The fix on the implementation side is quite easy, just change res.code to res.resp_code. However all the tests written in the traditional ways are broken, because they all mock this code. So all of them have to be changed. On the other hand, only one test written in code by units is broken because the resp.code is only mocked once. &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;In all these 3 rounds, the tests in the code in units fashion were less painful to work with, partly due to the fact the interaction between this class and the http library it uses. They also consistently provide a much better readability than the tests written in the traditional fashion. The only extra trade off we are taking here is the integration points between the private methods are not tested. There are two obvious risks associate with this: 1) existing hidden bug with these integration points not detected, and 2) future changes on these integration points will not be guarded. These are legitimate risks. &lt;br /&gt;&lt;br /&gt;However two arguments can be made here. First, as a result of the code by units fashion, these internal integrations points are very simple, mostly just simple delegations. Actually most of such integration points(in another word, private methods) were introduced due to this code by units fashion.  There shouldn't be much to test about them. Second, we often not test the integration between classes (by mocking one of the classes), and these class internal integrations are normally simpler and with definitely narrower scope. If we can take the risk of not testing the integration points between classes, why can't we take risk on these even "easier" integration points for the benefits? &lt;br /&gt;&lt;br /&gt;There is another trade off here, that is, since we are no longer doing BDD, we lost the class specs that can tell developers the behavior of it. The idea here(as mentioned in Section I) is that the implementation code in the class itself should be readable enough so that the behavior can be interpreted easily without going to its test. This idea might need more test than other ideas in this theory though. &lt;br /&gt; &lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;br /&gt;Congratulations!! if you reach here, your superior patience just won you a grand prize: a 25% discount on a two-hour personal boudoir package provided by the world famous photographer - Kai, checkout the detail out at &lt;a href=http://kaipic.com&gt;Kaipic.com&lt;/a&gt;. &lt;br /&gt; &lt;br /&gt;That aside, in conclusion, my pet theory is that first we can define readability, second we can use readability as a principle to guide coding activity and last the principle can be followed easier if we use a different way of coding, that is, code in small units. The trade off of this different way is that we no longer test the integration points inside a class, and my guess now is that it's gonna worth it. &lt;br /&gt;&lt;br /&gt;I will continue testing this pet theory in my personal projects (I fully understand that only in a real sized project, the benefits/shortcoming/feasibility can be truly exposed,) and keep you posted.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-7338802927031930843?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/7338802927031930843/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=7338802927031930843' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7338802927031930843'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7338802927031930843'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/10/different-way-of-coding-explained.html' title='A different way of coding explained.'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-1417610530764978092</id><published>2010-09-03T08:49:00.005-04:00</published><updated>2010-09-03T09:05:00.902-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Photography'/><category scheme='http://www.blogger.com/atom/ns#' term='LightMeter'/><category scheme='http://www.blogger.com/atom/ns#' term='Photography Assistant'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><title type='text'>Short update about Photography Assistant Beta</title><content type='html'>So far the feedbacks I got from users are pretty good. No major defects found. I did some testing myself, and I love the DoF calculator! (with manual exposure setting :)&lt;br /&gt;It's going to be a hectic month Sept for me. I wouldn't expect new features any time soon ( I might release 1.5 with some minor fixes ). But the I already decided what to add the next release - Photography Log, which will allow you to record all exposure setting (iso, shutter speed, aperture, subject distance, GPS location and even an optional cell phone camera pic of the motif) in your log. I would hope that this will be helpful mostly for film photography beginners. The GPS location might be helpful for most digital photographers too.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-1417610530764978092?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/1417610530764978092/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=1417610530764978092' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/1417610530764978092'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/1417610530764978092'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/09/short-update-about-photography.html' title='Short update about Photography Assistant Beta'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-8705256047213249995</id><published>2010-08-31T10:22:00.010-04:00</published><updated>2010-08-31T11:05:18.736-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='extreme programming'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><title type='text'>What drives my designing in TDD</title><content type='html'>Test Driven Development provides a very structural way for agile coding. One of the steps in TDD is refactor, and this is when we improve our design, but to what extent can it still be deemed as being agile? To remind myself, I would like to write down the *only* two factors that I think are agile to drive this design step:&lt;br /&gt;1) no duplicated code - if the new code is some sort of duplication of an existing logic, or it is destined to be reused by other developers in the team, I would improve the design to avoid (potential) duplication. &lt;br /&gt;2) no unreadable code - if the new code is hard to read, or it makes the existing file(s) difficult to read (for example,  the new code might make it harder to navigate), I would improve the design to achieve readability. &lt;br /&gt;&lt;br /&gt;That's it. If I find myself designing due to any factors other than these two, I would feel myself being less agile. It happens a lot, sometimes due to the limitations of the technical platform, sometimes due to the "process",  but I would keep reminding myself (and maybe the team) that it's not what I preferred.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-8705256047213249995?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/8705256047213249995/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=8705256047213249995' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/8705256047213249995'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/8705256047213249995'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/08/what-drives-my-designing-in-tdd.html' title='What drives my designing in TDD'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-5496982923163262233</id><published>2010-08-29T20:36:00.037-04:00</published><updated>2010-10-17T22:12:55.096-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='RSpec'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='unit test'/><title type='text'>Testing private methods in RSpec</title><content type='html'>Why testing private methods? Well, it's not really about private vs public, if you want really fine granular unit tests that always only test no more than a couple of lines of code, you will need to do partial mocking and test private methods.&lt;br /&gt;&lt;br /&gt;I heard argument that testing private methods exposes too much implementation and thus makes later refactoring harder. My argument is that unit test is part of the implementation. Fine grained "real unit" tests is very easy to read and understand. They help clarify the intent of that couple of lines of code in your target class. If you change implementation code, it should be perfect normal if you also need to change that simple unit test. On the other hand if your tests are in a larger granularity, then in each test, either you test a lot of code or your use a lot of mocking. Either case, chances are whenever you change implementation, you would need to change even more test code.&lt;br /&gt;&lt;br /&gt;Another common practice is to extract private methods to another class and make them public and test from there. &lt;a href="http://kailuowang.blogspot.com/2010/08/what-drives-my-designing-in-tdd.html"&gt;To me, there are only a few valid reasons to introduce a new class (or in general, to design)&lt;/a&gt;,  being able to test private methods isn't one of them. &lt;br /&gt;&lt;br /&gt;Alright, with excuses all said (your argument is welcome), here is how I test private methods in ruby with rspec. I defined a global method in a file called describe_internally.rb in my test folder&lt;br /&gt;&lt;span style="white-space: pre;"&gt;&lt;br /&gt;def describe_internally *args, &amp;amp;block&lt;br /&gt;    example = describe *args, &amp;amp;block&lt;br /&gt;    klass = args[0]&lt;br /&gt;    if klass.is_a? Class&lt;br /&gt;        saved_private_instance_methods = klass.private_instance_methods&lt;br /&gt;        example.before do&lt;br /&gt;             klass.class_eval { public *saved_private_instance_methods }&lt;br /&gt;        end&lt;br /&gt;        example.after do&lt;br /&gt;             klass.class_eval { private *saved_private_instance_methods }&lt;br /&gt;        end&lt;br /&gt;    end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;then whenever I need to test private methods for a class (say Foo), I use "describe_internally Foo" instead of "describe Foo". If you prefer, you can organize these two types of tests in the same file as the below example (say Foo is a class with two methods-a public one: "kick" and a private one: "aim" which returns the target to be kicked)&lt;br /&gt;&lt;span style="white-space: pre;"&gt;&lt;br /&gt;describe Foo do&lt;br /&gt;   describe "kick" do&lt;br /&gt;     it "should kick at where aim is" do &lt;br /&gt;        foo = Foo.new         &lt;br /&gt;        foo.should_receive(:aim).with(steve_jobs).and_return :a_place&lt;br /&gt;        foo.kick steve_jobs&lt;br /&gt;        steve_jobs.should be_kicked_at :a_place &lt;br /&gt;     end&lt;br /&gt;   end&lt;br /&gt;end&lt;br /&gt;describe_internally Foo do&lt;br /&gt;   describe "aim" do &lt;br /&gt;      it "should aim at where the butt is" do&lt;br /&gt;           Foo.new.aim(steve_jobs).should be steve_jobs.butt&lt;br /&gt;      end    &lt;br /&gt;   end&lt;br /&gt;end&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;(Although in this case, I would just put all specs under the describe_internally block, because the first spec also requires knowledge of the private method and from my experience there is seldom any problem caused by that)&lt;br /&gt;If you need an even more fine control of the scope of where you want private methods exposed, Jay Fields wrote a blog long ago giving another approach to achieve it &lt;a href="http://blog.jayfields.com/2007/11/ruby-testing-private-methods.html"&gt;http://blog.jayfields.com/2007/11/ruby-testing-private-methods.html&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-5496982923163262233?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/5496982923163262233/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=5496982923163262233' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5496982923163262233'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5496982923163262233'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/08/testing-private-methods-in-rspec.html' title='Testing private methods in RSpec'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-2404787138305081875</id><published>2010-08-21T23:42:00.006-04:00</published><updated>2010-10-06T11:47:41.560-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><category scheme='http://www.blogger.com/atom/ns#' term='unit test'/><title type='text'>Test Driven Development on Android</title><content type='html'>Although at the very beginning, TDD, especially the UI part, looked challenging on Android, now after about 90 tests and 4,5 epic stories, I am comfortably to say that Android app can be developed in a TDD fashion reasonably smooth. You just need to accept that you can't test the interaction between UI and domain model using mock framework. Then you design in a way so that such need will be reduced to minimum. Some Android API might not be test friendly, you can solve that by introducing some middle layer abstraction.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-2404787138305081875?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/2404787138305081875/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=2404787138305081875' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/2404787138305081875'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/2404787138305081875'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/08/test-driven-development-on-android.html' title='Test Driven Development on Android'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-5222162889231503733</id><published>2010-08-21T20:43:00.003-04:00</published><updated>2010-08-31T11:24:14.782-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Photography'/><category scheme='http://www.blogger.com/atom/ns#' term='LightMeter'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><title type='text'>Light Meter renamed to Photography Assistant, 1.5Beta released</title><content type='html'>I am very glad to announce that Photography Assistant 1.5 Beta was just released to the Android Market.&lt;br /&gt;Features:&lt;br /&gt;+ Aperture priority, Shutter priority and Manual mode for exposure setting calculation&lt;br /&gt;+ Auto Exposure Value (Light Meter function) using ambient light sensor&lt;br /&gt;+ Hyperfocal distance&lt;br /&gt;+ Depth of Field&lt;br /&gt;&lt;br /&gt;It was renamed from Light Meter to Photography Assistant. &lt;br /&gt;Please help test this Beta version.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-5222162889231503733?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/5222162889231503733/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=5222162889231503733' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5222162889231503733'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5222162889231503733'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/08/light-meter-renamed-to-photography.html' title='Light Meter renamed to Photography Assistant, 1.5Beta released'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-5685179624466014581</id><published>2010-08-10T08:47:00.003-04:00</published><updated>2010-08-31T11:24:32.655-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Photography'/><category scheme='http://www.blogger.com/atom/ns#' term='LightMeter'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><title type='text'>Update about Light Meter</title><content type='html'>3 weeks from the first release, Light Meter was downloaded more than 2,400 times.&lt;br /&gt;The development went smooth, I just need time. Currently I am working on adding Manual and Av mode, which is going to be release 1.1. The manual mode could be helpful for the app to work with an incident light meter.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-5685179624466014581?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/5685179624466014581/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=5685179624466014581' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5685179624466014581'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5685179624466014581'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/08/update-about-light-meter.html' title='Update about Light Meter'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-5756300642163633665</id><published>2010-07-29T18:25:00.004-04:00</published><updated>2010-08-31T11:24:45.848-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Photography'/><category scheme='http://www.blogger.com/atom/ns#' term='LightMeter'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><title type='text'>Light Meter 1.0 released on Android Market</title><content type='html'>The release of 1.0 EAP has been getting great responses. In less than 10 days, it got 866 downloads and 11 ratings with an average of 4.5 stars. So here is the 1.0 release.&lt;br /&gt;Changes&lt;br /&gt;+ calibration so that it can work with different ambient sensors on different phones. &lt;br /&gt;+ be able to remember settings&lt;br /&gt;Two help related features pushed to 1.01 &lt;br /&gt;Enjoy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-5756300642163633665?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/5756300642163633665/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=5756300642163633665' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5756300642163633665'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/5756300642163633665'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/07/light-meter-10-released-on-android.html' title='Light Meter 1.0 released on Android Market'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-8871437125971176047</id><published>2010-07-25T09:33:00.004-04:00</published><updated>2010-08-31T11:24:55.576-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Photography'/><category scheme='http://www.blogger.com/atom/ns#' term='LightMeter'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><title type='text'>One Polish comment on LightMeter 1.0 EAP</title><content type='html'>It says "Aplikacja która w przyszłości może będzie dobra, ale na razie jeszcze dużo jej brakuje."&lt;br /&gt;Google translate told me that it's Polish and in English it means "Application that the future can be good, but for now its still a lot missing."&lt;br /&gt;I guess this user must haven't seen the statement I put in the market: "This is an Early Access Preview version , so very premature yet."&lt;br /&gt;&lt;br /&gt;The commenter left a rating of 2 stars which changed the 100% 5-star rating so far (5 ratings before this one)&lt;br /&gt;&lt;br /&gt;Lesson learned: you can never go too far to manage user expectations.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-8871437125971176047?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/8871437125971176047/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=8871437125971176047' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/8871437125971176047'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/8871437125971176047'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/07/one-polish-comment-on-lightmeter-10-eap.html' title='One Polish comment on LightMeter 1.0 EAP'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-6173142033984863597</id><published>2010-07-19T23:41:00.003-04:00</published><updated>2010-08-31T11:25:47.039-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Photography'/><category scheme='http://www.blogger.com/atom/ns#' term='LightMeter'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><title type='text'>Road map for Light Meter</title><content type='html'>1.0  - add calibration function so that the app can work with ambient sensors from multiple manufactures. &lt;br /&gt;1.1 - add depth of field calculation&lt;br /&gt;1.2 - add hyperfocal distance calculation&lt;br /&gt;1.3 - add a depth of field priority mode.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-6173142033984863597?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/6173142033984863597/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=6173142033984863597' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/6173142033984863597'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/6173142033984863597'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/07/road-map-for-light-meter.html' title='Road map for Light Meter'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-7877504107868845003</id><published>2010-07-19T22:56:00.003-04:00</published><updated>2010-08-31T11:25:59.090-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Photography'/><category scheme='http://www.blogger.com/atom/ns#' term='LightMeter'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><title type='text'>Light Meter 1.0 EAP released.</title><content type='html'>Go to your Android Market and search for "Light Meter."&lt;br /&gt;It's an Early Access Preview version with only very basic functions. &lt;br /&gt;Give it a try and send me feedback if you would. I will really appreciate it. &lt;br /&gt;Thanks!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-7877504107868845003?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/7877504107868845003/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=7877504107868845003' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7877504107868845003'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7877504107868845003'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/07/light-meter-10-eap-released.html' title='Light Meter 1.0 EAP released.'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-7255407411762114749</id><published>2010-07-03T21:15:00.003-04:00</published><updated>2010-08-31T11:26:08.756-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Photography'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><title type='text'>Android UI Test note</title><content type='html'>If you are using the ant script to run the ui tests, that script only detects changes in the java code in the target and test project. The R resource file is somehow not included. If you make any change to the layout which requires some change in the R file (your IDE might be able to help you with that), if you then immediately run a simple "ant run-tests", you might get some weird error. It seems that the new layout file has taken effect while the R file has not. In this case, you need to run a "ant clean compile" in your target project and then "ant clean run-tests" in your ui test project.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-7255407411762114749?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/7255407411762114749/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=7255407411762114749' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7255407411762114749'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7255407411762114749'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/07/android-ui-test-note.html' title='Android UI Test note'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-2262661820074268696</id><published>2010-06-26T09:22:00.003-04:00</published><updated>2010-06-26T09:39:44.588-04:00</updated><title type='text'>Android LightMeter update</title><content type='html'>It seems that at least on EVO, the light meter reading is unstable, it could vary up to two steps in the same light situation. Also without the integration sphere the reading is too directional, which could also introduce an error of 1-2 steps. Other than calibration, some sort of alternative approach is needed. I am thinking about giving a guidance EV value table to assist.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-2262661820074268696?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/2262661820074268696/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=2262661820074268696' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/2262661820074268696'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/2262661820074268696'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/06/android-lightmeter-update.html' title='Android LightMeter update'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-2591160727751371426</id><published>2010-06-22T11:25:00.002-04:00</published><updated>2010-06-22T11:26:19.489-04:00</updated><title type='text'>MacPorts selfupdate not working behind firewall?</title><content type='html'>Here is the solution:&lt;br /&gt;Modify the last part of /opt/local/etc/macports/sources.conf as follows&lt;br /&gt;&lt;br /&gt;#  To get the ports tree from the master MacPorts server in California, USA use:&lt;br /&gt;#      rsync://rsync.macports.org/release/ports/&lt;br /&gt;#  To get it from the mirror in Trondheim, Norway use:&lt;br /&gt;#      rsync://trd.no.rsync.macports.org/release/ports/&lt;br /&gt;#  A current list of mirrors is available at http://trac.macports.org/wiki/Mirrors&lt;br /&gt;http://www.macports.org/files/ports.tar.gz [default]&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-2591160727751371426?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/2591160727751371426/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=2591160727751371426' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/2591160727751371426'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/2591160727751371426'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/06/macports-selfupdate-not-working-behind.html' title='MacPorts selfupdate not working behind firewall?'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-583444806430630124</id><published>2010-06-21T00:18:00.004-04:00</published><updated>2010-06-21T00:35:47.015-04:00</updated><title type='text'>Android Light Meter App</title><content type='html'>After one day of spiking, I now know that a light meter app is possible on Android (at least on EVO). I wrote an app that can display illumination value on screen. The next step will be adding an exposure value calculator to do aperture priority calculation.&lt;br /&gt;Although it's kind of using the same mechanism as&lt;span style="font-weight: bold;"&gt; &lt;/span&gt;&lt;strong&gt;&lt;/strong&gt;incident light meter, the accuracy of the light sensors on normal smart phones is probably not good enough to replace a professional incident light meter such as Sekonic ones. I haven't done a lot of testing yet but I already noticed that the accuracy is certainly not good enough for low light situations. On my HTC EVO, the accuracy below EV 6 is doubtful and it certainly can't tell anything below EV 2.&lt;br /&gt;With that in mind, I still think it could be helpful for day light metering especially for those who are using mechanic film cameras without any built-in light metering.&lt;br /&gt;I will keep everyone posted here.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-583444806430630124?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/583444806430630124/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=583444806430630124' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/583444806430630124'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/583444806430630124'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/06/android-light-meter-app.html' title='Android Light Meter App'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-7303727588175071166</id><published>2010-06-19T14:46:00.010-04:00</published><updated>2010-06-21T14:01:09.649-04:00</updated><title type='text'>UI testing on Android 2.1 - an simple example</title><content type='html'>This tests a simple button click action that sets the text of a TextView to "1".&lt;br /&gt;Several things were done in the following code to make it work 1) set focus on the button in the UIThread, 2) disable keyguard so that you can send the keyEvent&lt;br /&gt;&lt;br /&gt;&lt;span style="white-space: pre;"&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;public class MainWindowTest extends&lt;br /&gt;    ActivityInstrumentationTestCase2&lt;mainwindow&gt; {&lt;br /&gt;private Instrumentation mInstrumentation;&lt;br /&gt;private MainWindow mActivity;&lt;br /&gt;private Button mButton;&lt;br /&gt;private TextView mSensorReadView;&lt;br /&gt;&lt;br /&gt;public MainWindowTest() {&lt;br /&gt;       super("com.kaipic.lightmeter", MainWindow.class);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;protected void setUp() throws Exception {&lt;br /&gt; super.setUp();&lt;br /&gt; mInstrumentation = getInstrumentation();&lt;br /&gt; setActivityInitialTouchMode(false);&lt;br /&gt; mActivity = getActivity();&lt;br /&gt; mButton = (Button) mActivity.findViewById(R.id.read_button);&lt;br /&gt; mSensorReadView = (TextView) mActivity.findViewById(R.id.sensor_read_text_view);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public void testClickButton() {&lt;br /&gt; assertEquals("", mSensorReadView.getText());&lt;br /&gt; mActivity.runOnUiThread(new Runnable() {&lt;br /&gt;     public void run() {&lt;br /&gt;         mButton.requestFocus();&lt;br /&gt;     }&lt;br /&gt; });&lt;br /&gt; mInstrumentation.waitForIdleSync();&lt;br /&gt; this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);&lt;br /&gt; assertEquals("1", mSensorReadView.getText());&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;public class MainWindow extends Activity {&lt;br /&gt;&lt;br /&gt;public void onCreate(Bundle savedInstanceState) {&lt;br /&gt; super.onCreate(savedInstanceState);&lt;br /&gt; disableKeyGuardForTesting();&lt;br /&gt; setContentView(R.layout.main);&lt;br /&gt; Button readButton = (Button) findViewById(R.id.read_button);&lt;br /&gt; readButton.setOnClickListener(new View.OnClickListener() {&lt;br /&gt;     public void onClick(View v) {&lt;br /&gt;         TextView sensor_read_text_view = (TextView) findViewById(R.id.sensor_read_text_view);&lt;br /&gt;         sensor_read_text_view.setText("1");&lt;br /&gt;     }&lt;br /&gt; });&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private void disableKeyGuardForTesting() {&lt;br /&gt; KeyguardManager keyGuardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);&lt;br /&gt;      keyGuardManager.newKeyguardLock("com.kaipic.lightmeter.MainWindow").disableKeyguard();&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/mainwindow&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;in the AndroidManifest.xml you also need to have the following code under the tag manifest&lt;br /&gt;&lt;br /&gt;&amp;lt;uses-permission name="android.permission.DISABLE_KEYGUARD" /&amp;gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-7303727588175071166?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/7303727588175071166/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=7303727588175071166' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7303727588175071166'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7303727588175071166'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/06/ui-testing-on-android-21-simple-example.html' title='UI testing on Android 2.1 - an simple example'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-3422649361792487018</id><published>2010-06-15T09:14:00.000-04:00</published><updated>2010-06-15T09:15:06.010-04:00</updated><title type='text'>Email productivity trick - display the "important" Emails on the top</title><content type='html'>Gmail has a multi-inbox feature in the lab (you can enable it in  settings-lab and then go to settings-multiinbox to configure).&lt;br /&gt;With  this feature you can setup your own search query to display Emails in  groups in the inbox.&lt;br /&gt; For example, in my inbox now, I display the unread, non-spam and  non-project-team-group Emails in the top pane, all the unread  project-team-group Email in the second pane, and then everything else in  the bottom pane.&lt;br /&gt; This way, all the "importantly" Emails will mostly likely always show up  on the top. &lt;br /&gt;&lt;br /&gt;Here is the query string I am using if you are  interested:&lt;br /&gt;pane 0:     -label:project_name in:inbox is:unread  -subject:([All ThoughtWorks]) -subject:([spam]) &lt;br /&gt;  pane 1:     label:project_name is:unread&lt;br /&gt;&lt;br /&gt;"project_name" is the label I am  using for my project-team-group emails. I have filter that  automatically apply that label to Emails sent to that group. &lt;br /&gt;I hope  this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-3422649361792487018?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/3422649361792487018/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=3422649361792487018' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/3422649361792487018'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/3422649361792487018'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/06/email-productivity-trick-display.html' title='Email productivity trick - display the &quot;important&quot; Emails on the top'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-3225979255680176758</id><published>2010-06-10T10:51:00.004-04:00</published><updated>2010-06-10T11:09:27.466-04:00</updated><title type='text'>The concept of contributing</title><content type='html'>&lt;span class="z19Dle" id="col-z13ee1qpvkqggx3hb04cjrpanniahf1bbww0k"&gt;&lt;span class="zo"&gt;Every developer should work in a self-governed open source  project team for at least 3 months to understand the concept of  contributing, which has the following two sides:&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;ol&gt;&lt;li&gt;Ownership: in an self-governed open source project, every team member share the ownership of the whole project. If you want a new feature, you shall be able to add/modify any code any where to implement that without impacting other features.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Accountability: You are responsible to make sure every piece of the code you are going to commit is readable, tested. Because other people could work on your code, or has code change that might impact your code at any time.&lt;/li&gt;&lt;/ol&gt;&lt;span class="z19Dle" id="col-z13ee1qpvkqggx3hb04cjrpanniahf1bbww0k"&gt;&lt;span class="zo"&gt;Many developers only know how to finish a task assigned to  them, and thus lack the understanding of ownership and accountability. Plenty of software projects failed because of that.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="z19Dle" id="col-z13ee1qpvkqggx3hb04cjrpanniahf1bbww0k"&gt;&lt;span class="zo"&gt;&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-3225979255680176758?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/3225979255680176758/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=3225979255680176758' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/3225979255680176758'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/3225979255680176758'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2010/06/concept-of-contributing.html' title='The concept of contributing'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-2855680828062057238</id><published>2009-04-22T11:32:00.003-04:00</published><updated>2009-04-22T11:35:32.898-04:00</updated><title type='text'>Joined Thoughtworks</title><content type='html'>I am very glad to announce that I have joined Thoughtworks as a consultant starting April 20. 2009.&lt;br /&gt;I will continue contribute to the open source community. It's actually part of my job now.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-2855680828062057238?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/2855680828062057238/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=2855680828062057238' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/2855680828062057238'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/2855680828062057238'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2009/04/joined-thoughtworks.html' title='Joined Thoughtworks'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-4466270356478776655</id><published>2009-02-19T15:08:00.001-05:00</published><updated>2009-02-19T15:10:25.081-05:00</updated><title type='text'>Just released NHibernate.Burrow 1.0.2 GA</title><content type='html'>Here is the release note:&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;NHibernate.Burrow is a light weight middleware developed to support .Net applications using NHibernate (maybe also referred as NH in this article) as ORM framework by providing advanced and smart session/transaction management and other facilitates. Documentation and examples are available at &lt;a href="http://nhforge.net/" target="_new"&gt;http://NHForge.net&lt;/a&gt; Any feedback can be sent to NHibernate user group(&lt;a href="http://groups.google.com/group/nhusers" target="_new"&gt;http://groups.google.com/group/nhusers&lt;/a&gt;). Issues can be submitted to &lt;a href="http://http//jira.nhforge.org/" target="_new"&gt;http://http://jira.nhforge.org/&lt;/a&gt;  They will be greatly appreciated. Please go to &lt;a href="http://sourceforge.net/project/showfiles.php?group_id=216446&amp;amp;package_id=272688" target="_new"&gt;http://sourceforge.net/project/showfiles.php?group_id=216446&amp;amp;package_id=272688&lt;/a&gt; to download&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Build 1.0.2.GA&lt;br /&gt;========================&lt;br /&gt;Bug&lt;br /&gt;&lt;br /&gt;   * [NHB-14] - Ambiguous match found exception when loading statefulfield&lt;br /&gt;   * [NHB-16] - NHibernate.Burrow causes controls on the page to DataBind too early in the page cycle&lt;br /&gt;   * [NHB-21] - QueryString should not be parsed for Conversation information during postback&lt;br /&gt;&lt;br /&gt;Improvement&lt;br /&gt;&lt;br /&gt;   * [NHB-15] - Clean up transaction and session mangement code&lt;br /&gt;   * [NHB-23] - Improve WEbUtilHttpModule to ignore unecessary Handlers&lt;br /&gt;&lt;br /&gt;New Feature&lt;br /&gt;&lt;br /&gt;   * [NHB-17] - Provide ways to stop stateful field processor from traverse control tree&lt;br /&gt;   * [NHB-19] - An interceptor for configuring NHConfiguration before Burrow creates SessionFactory&lt;br /&gt;&lt;br /&gt;Patch&lt;br /&gt;&lt;br /&gt;   * [NHB-20] - Get a session by persistence unit name instead by class type&lt;br /&gt;   * [NHB-22] - Burrow throws a null reference exception when added as a module in IIS7 and static content is served&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-4466270356478776655?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/4466270356478776655/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=4466270356478776655' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/4466270356478776655'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/4466270356478776655'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2009/02/just-released-nhibernateburrow-102-ga.html' title='Just released NHibernate.Burrow 1.0.2 GA'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-6939579884325703181</id><published>2008-04-20T10:52:00.001-04:00</published><updated>2008-04-20T10:54:17.258-04:00</updated><title type='text'>NHibernate.Burrow 1.0.0 Alpha 1 is</title><content type='html'>The next generation of MindLib Core - NHibernate.Burrow just released its first version:&lt;br /&gt;1.0.0 Alpha 1 .&lt;br /&gt;&lt;p&gt;NHibernate.Burrow is a light weight middleware developed to support .Net&lt;br /&gt;applications using NHibernate (maybe also referred as NH in this article) as&lt;br /&gt;ORM framework by providing advanced and smart session/transaction management&lt;br /&gt;and other facilitates.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;For information please go:&lt;br /&gt;&lt;a target="_blank" rel="nofollow" href="http://nhcontrib.wiki.sourceforge.net/BurrowHome"&gt;http://nhcontrib.wiki.sourceforge.net/BurrowHome&lt;/a&gt;&lt;br /&gt;  &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-6939579884325703181?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/6939579884325703181/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=6939579884325703181' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/6939579884325703181'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/6939579884325703181'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2008/04/nhibernateburrow-100-alpha-1-is.html' title='NHibernate.Burrow 1.0.0 Alpha 1 is'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-7710983584400970484</id><published>2008-02-07T10:51:00.000-05:00</published><updated>2008-02-07T11:05:22.486-05:00</updated><title type='text'>Announcement</title><content type='html'>I am very glad to announce that I joined &lt;a href="https://sourceforge.net/projects/nhcontrib"&gt;NHibernate Contrib&lt;/a&gt; team and will be working with &lt;a href="https://sourceforge.net/users/ayenderahien/"&gt;Ayende Rahien&lt;/a&gt;, &lt;span style="text-decoration: underline;"&gt;D&lt;/span&gt;&lt;a href="https://sourceforge.net/users/darioquintana/"&gt;ario Quintana&lt;/a&gt;, &lt;span style="text-decoration: underline;"&gt;F&lt;/span&gt;&lt;a href="https://sourceforge.net/users/fabiomaulo/"&gt;abio Maulo&lt;/a&gt;, &lt;span style="text-decoration: underline;"&gt;K&lt;/span&gt;&lt;a href="https://sourceforge.net/users/karlchu/"&gt;arl Chu&lt;/a&gt; and &lt;a href="https://sourceforge.net/users/kpixel/"&gt;Pierre Henri Kuaté&lt;/a&gt;.&lt;br /&gt;Our goal is to deliver a set of tools to facilitate application development based on NHibernate.&lt;br /&gt;The core part of MindLib was extracted as a new proj called NHibernate.Burrow. Together with NHibernate.Shards, NHibernate.Validator and probably NHibernate.LINQ, it will become one of the new products scheduled to release with NHibernate 2.0.&lt;br /&gt;MindLib will continue being maintained as it's supporting NHibernate 1.2.X.  but there probably will be no MindLib 2.0.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-7710983584400970484?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/7710983584400970484/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=7710983584400970484' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7710983584400970484'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/7710983584400970484'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2008/02/announcement.html' title='Announcement'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-1823090277959329699</id><published>2008-01-10T13:26:00.001-05:00</published><updated>2008-01-10T13:27:17.320-05:00</updated><title type='text'>MindLib is also on Code Project now.</title><content type='html'>An article about MindLib is created on CodeProject.com.&lt;br /&gt;Here is the &lt;a href="http://www.codeproject.com/KB/library/MindLib_.aspx"&gt;link&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-1823090277959329699?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/1823090277959329699/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=1823090277959329699' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/1823090277959329699'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/1823090277959329699'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2008/01/mindlib-is-also-on-code-project-now.html' title='MindLib is also on Code Project now.'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-2757479665695721970</id><published>2007-12-26T19:33:00.000-05:00</published><updated>2007-12-26T21:23:36.103-05:00</updated><title type='text'>MindLib, Asp.net-NHibernate application development simplified!</title><content type='html'>As a real brief introduction, MindLib is a feather weight framework that helps glue Asp.net with NHibernate and thus dramatically simplify the development of Asp.net-NHibernate applications. Its main features includes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;transparent session management that supports both OpenSessionInView and LongSession pattern&lt;/strong&gt; - transparent here means that you don't needs to know what pattern to use, the framework will automatically chose one based on the current conversation status - see item 5.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;generic DAO base&lt;/strong&gt; that eliminates the need for developers to interact with NHibernate session - they don't even needs to know what NHibernate session is - guaranteed.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;easy stateful field support that make Asp.net UserControl and Page no longer stateless&lt;/strong&gt; - the values of particular fields of UserControl or Page will be persistent over multiple http requests. All you need to do is to mark a field of your UserControl or Page with an attribute. The life span of such fields can be fine grained.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;transparent multiple databases support&lt;/strong&gt;. "transparent" here means that your classes do not need to designate which database to use, the framework will automatically figure out. Basically you write your application like it's using one database.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;easy conversation support&lt;/strong&gt;. The meaning of the term "conversation" here is very close to the conversation concept introduced in the &lt;a class="wiki_link_ext" href="http://www.jboss.com/products/seam" rel="nofollow"&gt;Seam &lt;/a&gt;framework. Easy here means that you don't need to define a conversation - you just call the framework to start a long conversation and call the framework to finish it when you are done - only two lines of code is needed.&lt;/li&gt;&lt;/ul&gt; MindLib is already setup as GPL open source project on SF.net and I just released 1.3 Beta1.&lt;br /&gt;Now I am building the&lt;a href="http://mindlib.sf.net"&gt; project wiki site&lt;/a&gt; where you can find more information about MindLib.&lt;br /&gt;I already finished the &lt;a href="http://mindlib.wiki.sourceforge.net/Introduction"&gt;introduction &lt;/a&gt;and &lt;a href="http://mindlib.wiki.sourceforge.net/QuickStart"&gt;quick start guide&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-2757479665695721970?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/2757479665695721970/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=2757479665695721970' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/2757479665695721970'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/2757479665695721970'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2007/12/mindlib-aspnet-nhibernate-application.html' title='MindLib, Asp.net-NHibernate application development simplified!'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-4528030502229189735</id><published>2007-12-18T21:46:00.000-05:00</published><updated>2007-12-18T21:47:38.488-05:00</updated><title type='text'>MindLib is setup on SF.net!!!!</title><content type='html'>Here is the Url: http://sourceforge.net/projects/mindlib/&lt;br /&gt;The code is already available on svn.&lt;br /&gt;Wiki site will be built shortly.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-4528030502229189735?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/4528030502229189735/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=4528030502229189735' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/4528030502229189735'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/4528030502229189735'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2007/12/mindlib-is-setup-on-sfnet.html' title='MindLib is setup on SF.net!!!!'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-1459208658158544298</id><published>2007-12-17T22:20:00.001-05:00</published><updated>2007-12-17T22:20:57.138-05:00</updated><title type='text'>Be prepared for an new SF open source project</title><content type='html'>MindLib -A light weight library that facilitates ajax.asp.net applications built on NHibernate framework by providing transparent support&lt;br /&gt;for 2 major session/transaction patterns: Open Session In View and Conversation(a seam concept) and a lot more.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-1459208658158544298?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/1459208658158544298/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=1459208658158544298' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/1459208658158544298'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/1459208658158544298'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2007/12/be-prepared-for-new-sf-open-source.html' title='Be prepared for an new SF open source project'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-115094021339387144</id><published>2006-06-21T21:31:00.000-04:00</published><updated>2006-06-21T21:36:53.403-04:00</updated><title type='text'>NHibernate debugging Could not save object</title><content type='html'>&lt;p&gt; &lt;/p&gt;&lt;p&gt;&lt;span style="font-size:85%;color:#666666;"&gt;NHibernate.Util.ADOExceptionReporter [(null)] &lt;(null)&gt; - Could not save NHibernate.Util.ADOExceptionReporter [(null)] &lt;(null)&gt; - Could not save objectSystem.NullReferenceException: Object reference not set to an instance of an object.   at NHibernate.Property.BasicSetter.Set(Object target, Object value)   at NHibernate.Persister.AbstractEntityPersister.SetPropertyValues(Object obj, Object[] values)   at NHibernate.Impl.SessionImpl.DoSave(Object theObj, Key key, IClassPersister persister, Boolean replicate, Boolean useIdentityColumn, CascadingAction cascadeAction, Object anything)   at NHibernate.Impl.SessionImpl.DoSave(Object obj, Object id, IClassPersister persister, Boolean useIdentityColumn, CascadingAction cascadeAction, Object anything)   at NHibernate.Impl.SessionImpl.SaveWithGeneratedIdentifier(Object obj, CascadingAction action, Object anything)2006-06-21 21:15:41,187 [5260] WARN  NHibernate.Util.ADOExceptionReporter [(null)] &lt;(null)&gt; - System.NullReferenceException: Object reference not set to an instance of an object.   at NHibernate.Property.BasicSetter.Set(Object target, Object value)   at NHibernate.Persister.AbstractEntityPersister.SetPropertyValues(Object obj, Object[] values)   at NHibernate.Impl.SessionImpl.DoSave(Object theObj, Key key, IClassPersister persister, Boolean replicate, Boolean useIdentityColumn, CascadingAction cascadeAction, Object anything)   at NHibernate.Impl.SessionImpl.DoSave(Object obj, Object id, IClassPersister persister, Boolean useIdentityColumn, CascadingAction cascadeAction, Object anything)   at NHibernate.Impl.SessionImpl.SaveWithGeneratedIdentifier(Object obj, CascadingAction action, Object anything)&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-size:85%;color:#666666;"&gt;&lt;/span&gt; &lt;/p&gt;&lt;p&gt;&lt;span style="color:#000000;"&gt;The reason is that one of the mapping property does not have a setter, it only has a getter.&lt;br /&gt;This is a hard one to find because normally Nhibernate will tell you that it cannot find the setter of the property, however, instead, it throws a null reference error.&lt;br /&gt;The way with which I found this is to comment out some part of the mapping file and see if the error still occurs when this part of the mapping file is comment out. So I can locate which mapping property is causing the error. &lt;/span&gt;&lt;/p&gt;&lt;p&gt; &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-115094021339387144?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/115094021339387144/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=115094021339387144' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/115094021339387144'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/115094021339387144'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2006/06/nhibernate-debugging-could-not-save.html' title='NHibernate debugging Could not save object'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-114393027342509152</id><published>2006-04-01T17:24:00.000-05:00</published><updated>2006-04-01T17:24:33.436-05:00</updated><title type='text'>How to avoid over generalization</title><content type='html'>Signs of over generalization:&lt;br /&gt;1)      Most important standard: what your system can do with the generalized type? For an example: If you generalize Customer and Administrator to a type called Human, what your system can do with a Human type object? If there is very little, then you probably should remove the Human type to simplify your system.&lt;br /&gt;2)      The generalized type itself has little work. What code you can put in the Human type?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-114393027342509152?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/114393027342509152/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=114393027342509152' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/114393027342509152'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/114393027342509152'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2006/04/how-to-avoid-over-generalization.html' title='How to avoid over generalization'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-114122703626958226</id><published>2006-03-01T10:30:00.000-05:00</published><updated>2006-03-01T10:30:36.306-05:00</updated><title type='text'>javascript error in ASP.NET</title><content type='html'>javascript error in asp.net page&lt;br/&gt;check if you use &amp;lt;!— --!&amp;gt; to comment out any asp controls.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-114122703626958226?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/114122703626958226/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=114122703626958226' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/114122703626958226'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/114122703626958226'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2006/03/javascript-error-in-aspnet.html' title='javascript error in ASP.NET'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-114115367194348129</id><published>2006-02-28T14:07:00.000-05:00</published><updated>2006-02-28T14:07:51.953-05:00</updated><title type='text'>NHibernate debug</title><content type='html'>NHibernate “Can’t save to null “ adoexception&lt;br/&gt;When you failed to maintain your relationship between entities correctly, and there is a cascade saveupdate, you will probably see this error. &lt;br/&gt;&amp;lt;class husbande&amp;gt;&lt;br/&gt;&amp;lt;one-to-one class=wife cascading=save-update&amp;gt;&lt;br/&gt;….&lt;br/&gt;Husband tom = new Husband();&lt;br/&gt;Wife jane = new Wife();&lt;br/&gt;tom.wife = jane; &lt;br/&gt;sess.SaveOrUpdate(tom)&lt;br/&gt;&lt;br/&gt;since you missed the&amp;nbsp;&amp;nbsp;jane.husband = tom;&lt;br/&gt;you will get that err.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-114115367194348129?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/114115367194348129/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=114115367194348129' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/114115367194348129'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/114115367194348129'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2006/02/nhibernate-debug.html' title='NHibernate debug'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-114115320451183181</id><published>2006-02-28T14:00:00.000-05:00</published><updated>2006-02-28T14:00:04.566-05:00</updated><title type='text'>Aspnet 20 debug</title><content type='html'>Asp.net 2.0 debug&lt;br/&gt;&lt;br/&gt;If you saw a runtime error keep asking you to turn the customErrorPage mode=off, no matter it already set as off, it probably means that your web.config is corrupt.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-114115320451183181?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/114115320451183181/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=114115320451183181' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/114115320451183181'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/114115320451183181'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2006/02/aspnet-20-debug.html' title='Aspnet 20 debug'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-114082303591954463</id><published>2006-02-24T18:17:00.000-05:00</published><updated>2006-02-24T18:17:15.976-05:00</updated><title type='text'>ASP.NET 2.0 Building model</title><content type='html'>In Asp.net 2.0 every control and page is in its own namespace. In the code, you won’t be able to reference to another control without register it in the ascx page.  Ugly, absolutely ugly.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-114082303591954463?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/114082303591954463/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=114082303591954463' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/114082303591954463'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/114082303591954463'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2006/02/aspnet-20-building-model.html' title='ASP.NET 2.0 Building model'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-114054507417996313</id><published>2006-02-21T13:04:00.000-05:00</published><updated>2006-02-21T13:07:12.483-05:00</updated><title type='text'>Running Asp.net 2.0 and 1.1 on IIS 6.0</title><content type='html'>From &lt;a class="headermaintitle" id="_ctl0__ctl0__ctl0__ctl0_BlogTitleHeader1__ctl0_BlogTitle" href="http://www.irishdev.com/blogs/jbrennan/default.aspx"&gt;John Brennan's Blog&lt;/a&gt;&lt;br /&gt;Configuration tip: running ASP.NET 2.0 apps on IIS 6.0&lt;br /&gt;&lt;br /&gt;This is something I came across this morning when deploying an ASP.NET 2.0 application. I installed .net framework beta 2 on to the box. I then configured the site through IIS to use .NET 2.0 version of the framework. (For those who aren't aware of this, .NET 2.0 inserts a new tab into your web site properties in IIS. This tab which is called "ASP.NET" enables you to set the version of the framework under which your application will run. It also has a few other cool features such as editing your web.config file through the UI).&lt;br /&gt;Anyhow deployed the app and an hour later I got this error saying: ASP.NET Application Service is unavailable. First thing was check the event log on the IIS box which had a really informative error message:&lt;br /&gt;It is not possible to run two different versions of ASP.NET in the same IIS process. Please use the IIS Administration Tool to reconfigure your server to run the application in a separate process. Please use the IIS Administration Tool to reconfigure your server to run the application in a separate process.&lt;br /&gt;The solution from TechNet site was to create a seperate application pool for .NET 2.0 applications. My existing v1.1 apps continue to run in the defaultAppPool while my .NET 2.0 app now uses my AspNet2AppPool. The steps involved were:&lt;br /&gt;A. To create a new Application Pool in IIS 6.0&lt;br /&gt;Open the IIS management console and expand the local computer by clicking the plus sign.&lt;br /&gt;Right-click the Application Pools folder, point to New, and then click Application Pool. The Add New Application Pool dialog box appears.&lt;br /&gt;Enter the new pool name in the Application pool text box, and then click OK.&lt;br /&gt;B. To assign my ASP.NET application to the new Application Pool&lt;br /&gt;Open the IIS management console, expand the local computer by clicking the plus sign, and navigate to the folder that contains the ASP.NET application.&lt;br /&gt;Right-click the application and then click Properties. The application's properties dialog box appears.&lt;br /&gt;On the Directory tab, select the desired pool designation from the Application Pool list.&lt;br /&gt;Further information available at: &lt;a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconconfiguringaspnetapplicationforaspnetversion.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconconfiguringaspnetapplicationforaspnetversion.asp&lt;/a&gt;&lt;br /&gt;posted on Thursday, October 27, 2005 9:22 AM by &lt;a id="_ctl0__ctl0__ctl0__ctl0__ctl0__ctl0_Entry__ctl0_AuthorLink" href="http://www.irishdev.com/user/Profile.aspx?UserID=1006"&gt;jbrennan&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-114054507417996313?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/114054507417996313/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=114054507417996313' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/114054507417996313'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/114054507417996313'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2006/02/running-aspnet-20-and-11-on-iis-60.html' title='Running Asp.net 2.0 and 1.1 on IIS 6.0'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-114004066158997098</id><published>2006-02-15T16:55:00.000-05:00</published><updated>2006-02-20T23:23:20.850-05:00</updated><title type='text'>NHibernate debugging</title><content type='html'>If you keep getting an error like the following:&lt;br /&gt;Exception Details: NHibernate.HibernateException: Cannot instantiate abstract class or interface: XXX.XXX.ISomeInterface&lt;br /&gt;It's probably caused by the broken of the data integrity of the database, somebody has modified your database manually.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-114004066158997098?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/114004066158997098/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=114004066158997098' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/114004066158997098'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/114004066158997098'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2006/02/nhibernate-debugging.html' title='NHibernate debugging'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-113910414219895201</id><published>2006-02-04T20:49:00.000-05:00</published><updated>2006-02-20T23:26:48.073-05:00</updated><title type='text'>Generic Sets and Collection Wrapper</title><content type='html'>&lt;link href="http://www.codeproject.com/styles/global.css" type="text/css" rel="stylesheet"&gt;&lt;ul class="download"&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.codeproject.com/csharp/GenericISet/GenericSets.zip"&gt;Download source code - 86.9 Kb&lt;/a&gt; &lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h2&gt;Introduction&lt;/h2&gt;&lt;br /&gt;&lt;p&gt;This simple library is developed for working with NHibernate 1.1 or lower. If there is no benefit of having a generic &lt;code&gt;Set&amp;lt;T&amp;gt;&lt;/code&gt; that also implements &lt;code&gt;Iesi.Collections.ISet&lt;/code&gt; from NHibnernate, you might be more interested in the &lt;a href="http://www.itu.dk/research/c5/" target="_blank"&gt;C5 collection library&lt;/a&gt; or the &lt;a href="http://www.wintellect.com/powercollections/" target="_blank"&gt;PowerCollection&lt;/a&gt;.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;This is a simple extension to &lt;b&gt;&lt;a href="http://www.codeproject.com/csharp/sets.asp"&gt;JasonSmith&lt;/a&gt;&lt;/b&gt;'s &lt;a href="http://www.codeproject.com/csharp/sets.asp"&gt;&lt;code&gt;Iesi.Collections&lt;/code&gt;&lt;/a&gt; to offer a generic version of the &lt;code&gt;ISet&lt;/code&gt; and its implementations. Also included are five wrappers which help in using old non-generic collections as generic collections. This could be helpful if you want to wrap a non-generic collection from an old application as generic ones in your application.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Like in .NET 1.1, there is need for a generic &lt;code&gt;ISet&lt;/code&gt; in the .NET 2.0 environment. There are already some implementations based on JasonSmith's famous &lt;code&gt;Iesi.Collections.ISet&lt;/code&gt;. One of them was found on &lt;a href="http://jira.nhibernate.org/browse/NH-536" target="_blank"&gt;NHibernate' JIRA&lt;/a&gt; which is, according to G77 (thanks!), authored by David Marquam. The implementation of this article is basically a modified version of that implementation. I would like to make it clear that most of the credit of this extension goes to him. I posted this version here so that more people can take advantage of this nice work.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;However, David's implementation could not perfectly meet our needs because of several facts:&lt;/p&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;The &lt;code&gt;ISet&amp;lt;T&amp;gt;&lt;/code&gt; in this implementation also inherits the &lt;code&gt;ISet&lt;/code&gt;. This inheritance caused the lost of the type safe characteristic that a generic collection normally has.&lt;br /&gt;&lt;li&gt;The &lt;code&gt;ISet&amp;lt;T&amp;gt;&lt;/code&gt; does not have the &lt;code lang="cs"&gt;bool Add(T o)&lt;/code&gt; method like the &lt;code&gt;ISet&lt;/code&gt; has. It only has the &lt;code lang="cs"&gt;void ICollection&amp;lt;T&amp;gt;.Add(T o)&lt;/code&gt;; the ability to tell whether the item has been actually added might be missed.&lt;br /&gt;&lt;li&gt;The generic extension was included in the assembly of JasonSmith’s &lt;code&gt;Iesi.Collection&lt;/code&gt;, which makes some difficulty in working with the original &lt;code&gt;Iesi.Collection&lt;/code&gt;. &lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h2&gt;Using the code&lt;/h2&gt;&lt;br /&gt;&lt;p&gt;Thus I decided to modify this implementation so that the &lt;code&gt;ISet&amp;lt;T&amp;gt;&lt;/code&gt; looks like the following:&lt;/p&gt;&lt;pre lang="cs"&gt;public interface ISet&amp;lt;T&amp;gt; : ICollection&amp;lt;T&amp;gt;, ICloneable {…}&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;In the meantime, I think it could be very helpful if the base &lt;code&gt;Set&lt;/code&gt; class still implements the &lt;code&gt;ISet&lt;/code&gt; interface since it has been widely applied, including by NHibernate. In this way, you can still have the collection mapped by NHibernate.&lt;/p&gt;&lt;pre lang="cs"&gt;public abstract class Set&amp;lt;T&amp;gt; : ISet&amp;lt;T&amp;gt;, ISet {…}&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;As a reminder, here is the declaration of &lt;code&gt;ISet&lt;/code&gt; from &lt;code&gt;Iesi.Collections&lt;/code&gt;:&lt;/p&gt;&lt;pre lang="cs"&gt;public abstract class ISet : ICollection, ICloneable {…}&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;For the usage of this &lt;code&gt;ISet&amp;lt;T&amp;gt;&lt;/code&gt;, please refer to JasonSmith's article about his &lt;a href="http://www.codeproject.com/csharp/sets.asp"&gt;&lt;code&gt;Iesi.Collections&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Since &lt;code&gt;Set&amp;lt;T&amp;gt;&lt;/code&gt; brought the scenario where a generic collection needs to work with old non-generic collections, I added five simple wrappers:&lt;/p&gt;&lt;pre lang="cs"&gt;public struct EnumeratorWrapper&amp;lt;T&amp;gt; : IEnumerator&amp;lt;T&amp;gt;{…}&lt;br /&gt;public class EnumerableWrapper &amp;lt;T&amp;gt; : IEnumerable&amp;lt;T&amp;gt;&lt;br /&gt;public sealed class SetWrapper&amp;lt;T&amp;gt; : ISet&amp;lt;T&amp;gt;&lt;br /&gt;public class CollectionWrapper&amp;lt;T&amp;gt; : EnumerableWrapper&amp;lt;T&amp;gt;, ICollection&amp;lt;T&amp;gt;&lt;br /&gt;public class ListWrapper&amp;lt;T&amp;gt; : EnumerableWrapper&amp;lt;T&amp;gt;, IList&amp;lt;T&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;These wrappers support using regular collections under a generic collection interface by wrapping the regular collection as an inner collection and delegate all the functions to it. Also, the &lt;code&gt;Equals()&lt;/code&gt; of the wrapper are also overridden to delegate to the wrapped, so that &lt;code&gt;wrapperA.Equals(wrapperB)&lt;/code&gt; will return &lt;code lang="cs"&gt;true&lt;/code&gt; when and only when &lt;code&gt;wrappedA.Equals(wrappedB)&lt;/code&gt; is true. The usage of these wrappers are very simple, here is a sample code:&lt;/p&gt;&lt;pre lang="cs"&gt;IList = new ArrayList(3);&lt;br /&gt;list.Add("one");&lt;br /&gt;list.Add("two");&lt;br /&gt;list.Add("three");&lt;br /&gt;&lt;br /&gt;ICollection&amp;lt;string&amp;gt; cln = new CollectionWrapper&amp;lt;string&amp;gt;(list);&lt;br /&gt;IEnumerable&amp;lt;string&amp;gt; enl = new EnumerableWrapper&amp;lt;string&amp;gt;(list);&lt;br /&gt;IList&amp;lt;string&amp;gt; lst = new ListWrapper&amp;lt;string&amp;gt;(list);&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;The &lt;code&gt;Iesi.Collections.Generic&lt;/code&gt; is in an independent assembly so that use can have more flexibility with using this together with the &lt;code&gt;Iesi.Collection.Generic&lt;/code&gt;.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;I will keep working on the implementations since my development greatly relies on them.&lt;/p&gt;&lt;br /&gt;&lt;h2&gt;Important Notes&lt;/h2&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;This implementation is based on the source code from NHibernate which does not override the &lt;code&gt;Equals&lt;/code&gt; method, so the &lt;code&gt;a.Equals(b)&lt;/code&gt; in this implementation will only return &lt;code lang="cs"&gt;true&lt;/code&gt; if &lt;code lang="cs"&gt;a==b;&lt;/code&gt;.&lt;br /&gt;&lt;li&gt;The generic &lt;code&gt;SynchornoizedSet&lt;/code&gt; has not been implemented yet. &lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h2&gt;Latest Update:&lt;/h2&gt;&lt;br /&gt;&lt;h3&gt;Feb 6, 06&lt;/h3&gt;&lt;br /&gt;&lt;p&gt;I added the &lt;code&gt;Iesi.Collections.Test&lt;/code&gt; from Nhibernate1.0.2.0. 67 out of the 87 tests were passed using the generic implementation.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;The four &lt;code&gt;ExclusiveOR&lt;/code&gt; tests could not be passed before I did a very minor modification to the original &lt;code&gt;Iesi.Collections&lt;/code&gt;: in the original &lt;code&gt;Set&lt;/code&gt;, the method &lt;code&gt;ExclusiveOr&lt;/code&gt; was written as follows:&lt;/p&gt;&lt;pre lang="cs"&gt;public static ISet ExclusiveOr(ISet a, ISet b)&lt;br /&gt;{&lt;br /&gt;    if(a == null &amp;amp;&amp; b == null)&lt;br /&gt;       return null;&lt;br /&gt;     else if(a == null)&lt;br /&gt;       return (Set)b.Clone();&lt;br /&gt;     else if(b == null)&lt;br /&gt;        return (Set)a.Clone();&lt;br /&gt;     else&lt;br /&gt;       return a.ExclusiveOr(b);&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Note that the clones of &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; are unnecessarily down cast to &lt;code&gt;Set&lt;/code&gt;. While, in the &lt;code&gt;Union&lt;/code&gt; method of this class, these two clones are down cast to &lt;code&gt;ISet&lt;/code&gt;. I modify the original code as follows:&lt;/p&gt;&lt;pre lang="cs"&gt;return (ISet)b.Clone();&lt;br /&gt;...&lt;br /&gt;return (ISet)a.Clone();&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;After this modification, the &lt;code&gt;Iesi.Collection&lt;/code&gt; still passes all the &lt;code&gt;Iesi.Collection.Test&lt;/code&gt;, and the generic implementation also passes the four &lt;code&gt;ExclusiveOr&lt;/code&gt; tests.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Latest update: This small bug of &lt;code&gt;Iesi.Collection.Set&lt;/code&gt; has been fixed in the NHibernate version 1.1-alpha1 &lt;small&gt;[ 10081 ]&lt;/small&gt;, so you don't need to worry about this problem if you are using &lt;code&gt;Iesi.Collection&lt;/code&gt; from the later versions of NHibernate.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;There are still 16 tests that cannot be passed. They are all operator tests. Since operators can only be used between classes not interfaces, the operator tests down cast the &lt;code&gt;ISet&lt;/code&gt; to &lt;code&gt;Set&lt;/code&gt; to do the tests, and our generic implementation cannot be downcast to the non-generic &lt;code&gt;Set&lt;/code&gt; which causes the failure of the 16 tests.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-113910414219895201?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/113910414219895201/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=113910414219895201' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/113910414219895201'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/113910414219895201'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2006/02/generic-sets-and-collection-wrapper.html' title='Generic Sets and Collection Wrapper'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-113867589932997425</id><published>2006-01-30T21:51:00.000-05:00</published><updated>2006-01-30T21:51:39.383-05:00</updated><title type='text'>Don't use Visible property to store any state for a control</title><content type='html'>For .NET (1.1, 2.0) controls, do not use the property “Visible” to store any information. If the parent control’s Visible is set to false, you won’t be able to set the subControls’ visibility to true. And there will be no exception thrown out. The system just ignores your &lt;br/&gt;&amp;lt;code&amp;gt;MyControl.Visible = true;&amp;lt;/code&amp;gt; if any of the parent or parent of parent control’s Visibility is false.&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-113867589932997425?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/113867589932997425/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=113867589932997425' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/113867589932997425'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/113867589932997425'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2006/01/dont-use-visible-property-to-store-any.html' title='Don&apos;t use Visible property to store any state for a control'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-113866266226181765</id><published>2006-01-30T18:11:00.000-05:00</published><updated>2006-01-30T18:11:02.266-05:00</updated><title type='text'>Using Java API in .NET</title><content type='html'>Using Java API in .Net application&lt;br/&gt;&lt;br/&gt;&lt;span style="font-family:Verdana;"&gt;IKVM.NET is an implementation of Java for &lt;/span&gt;&lt;a href="http://www.go-mono.org/"&gt;Mono&lt;/a&gt;&lt;span style="font-family:Verdana;"&gt; and the &lt;/span&gt;&lt;a href="http://msdn.microsoft.com/netframework/"&gt;Microsoft .NET Framework&lt;/a&gt;&lt;span style="font-family:Verdana;"&gt;. It includes the following components:&lt;/span&gt;&lt;br/&gt;&lt;ol&gt;&lt;li&gt;&lt;span style="font-family:Verdana;"&gt;A Java Virtual Machine implemented in .NET &lt;/span&gt;&lt;/li&gt;&lt;br/&gt;&lt;li&gt;&lt;span style="font-family:Verdana;"&gt;A .NET implementation of the Java class libraries &lt;/span&gt;&lt;/li&gt;&lt;br/&gt;&lt;li&gt;&lt;span style="font-family:Verdana;"&gt;Tools that enable Java and .NET interoperability &lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;&lt;span style="font-family:Verdana;"&gt;The following article states how to use &lt;/span&gt;&lt;span style="font-family:Verdana;"&gt;IKVM.NET Bytecode Compiler (&lt;/span&gt;&lt;span style="font-family:Verdana;"&gt;ikvmc.exe) to convert a java API to .NET CIL (dll’s and exe’s). &lt;/span&gt;&lt;br/&gt;http://www.ikvm.net/userguide/ikvmc.html&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-113866266226181765?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/113866266226181765/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=113866266226181765' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/113866266226181765'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/113866266226181765'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2006/01/using-java-api-in-net.html' title='Using Java API in .NET'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-113824063983323863</id><published>2006-01-25T20:57:00.000-05:00</published><updated>2006-02-02T13:51:31.676-05:00</updated><title type='text'>Setting up Subversion in VS.Net/Windows environment</title><content type='html'>Setting up Subversion in VS.Net/Windows environment&lt;br /&gt;&lt;br /&gt;This short article is just about client side setup, for server side installation, here is a real good &lt;a href="http://blogs.clearscreen.com/migs/archive/2005/01/21/824.aspx"&gt;article from Miguel Jimenez’s blog&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;we’ll need the following softwares:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;&lt;a href="http://tortoisesvn.tigris.org/download.html"&gt;TortoiseSVN&lt;/a&gt;, a Subversion Administration interface that integrates with Windows Explorer as a Shell Extension. &lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://rapidsvn.tigris.org/servlets/ProjectDocumentList?folderID=184"&gt;RapidSVN&lt;/a&gt;, a GUI Subversion Client application if you don’t prefer shell extension.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://ankhsvn.tigris.org/"&gt;AnkhSVN&lt;/a&gt; a Subversion addin for Microsoft Visual Studio .Net &lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;This article is based on my experience with &lt;a href="http://prdownloads.sourceforge.net/tortoisesvn/TortoiseSVN-1.3.1.5521-svn-1.3.0.msi?download"&gt;TortoiseSVN-1.3.1.5521&lt;/a&gt; , &lt;a href="http://ankhsvn.tigris.org/files/documents/764/15748/AnkhSetup-0.5.5.1653.msi"&gt;Ankh 0.5.5 &lt;/a&gt;and VisualStudio .Net 2003 on WindowsXP.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;1, install TortoisesSVN, should be painless without any difficult setting to do.&lt;br /&gt;&lt;br /&gt;2, before we begin with any project, let’s do some settings in TortoisesSVN, right click on any where in an windows explorer, select TortoisesSVN ( settings. In the general page there is a textbox called Global ignore pattern, if you are mostly working with .Net projects, you can set this setting as the following:&lt;br /&gt;*.tmp *.bak */bin */obj bin obj *.~?? *.suo *.user *.webinfo */CVS CVS *cvsignore* *resharper* .#* *ReSharper* Ankh.*Load&lt;br /&gt;All the files whose file name conforms to these pattern will be ignored by SVN.&lt;br /&gt;Of course you can add or remove patterns, just use space to separate them. Click OK to apply the change.&lt;br /&gt;&lt;br /&gt;3, now we can bring a project under control (for project that is already on the server, you just need a simple checkout ) I assume that a repository is already setup on the server. Say you have a project called MyProject&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Right click on any where in an windows explorer, select TortoisesSVN ( Repo-browser. In the Repository Browser, right click on the folder where you want to place you project ( commonly YourRepositoryName/trunk ), select create folder. Enter “MyProject” as the name. Then click OK to exit.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Use windows explorer to browse where your project located ( Visual Studio Projects\MyProject\), within the project folder, right click on blank and select SVN Checkout. In the url of repository enter the url of the folder you just created, for example: svn://yourserver.com/YourRepostioryName/trunk/MyProject, and click OK. You will get an alert window, saying the target folder is not empty! Blah blah… click Yes. Then Tortoise will do a real quick checkout, and create a “_svn” folder in the project folder.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Now if you right click on blank space of your project folder in windows explorer, you will see the SVN Commit option, click it. In the popup commit window make sure you check everything you want to include in the control and uncheck everything that you don’t (for example web.config/ App.config). click OK and go get a cup of coffee.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;After committing is done, under the project folder, select SVN commit again, this time you will see all the items that you don’t want to be included in the source control, select them and right click on them chose ignore then you can select to ignore the file only or all the files with the same extension. &lt;/li&gt;&lt;/ul&gt;Done with the TortoiseSVN part, lets go to the VS.net addin &lt;a href="http://ankhsvn.tigris.org/"&gt;AnkhSVN&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;4, After installed AnkhSVN, open VS.net, go to Tools( AnkhSVN ( Edit the AnkhSVN config file, you will see the configuration file, at the end of the file you will see a sentence&lt;br /&gt;&amp;lt;!-- &lt;span style="font-family:Courier New;font-size:85%;"&gt;&amp;lt;AdminDirectoryName&amp;gt;_svn&amp;lt;/AdminDirectoryName&amp;gt;--&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:+0;"&gt;If your TortoiseSVN is using _SVN as the adminDirectory please&lt;/span&gt;&lt;br /&gt;uncomment this sentence so it becomes:&lt;br /&gt;&lt;span style="font-family:Courier New;font-size:85%;"&gt;&amp;lt;Ad_inDirectoryName&amp;gt;_svn&amp;lt;/Ad_inDirectoryName&amp;gt;&lt;/span&gt;&lt;br /&gt;This way the AnkhSVN will work with TortoiseSVN well.&lt;br /&gt;Now open you project, AnkhSVN will popup a messagebox saying it has detected that this project is under the Subversion Control, do you like to enable AnkhSVN for this project. Off course, click yes.&lt;br /&gt;&lt;br /&gt;Now you will be able to commit or update in the VS.net, if you add/remove something, it will also be automatically add/removed to/from the Subversion. You just need to commit the whole solution. Sometimes you will see question mark which probably means that some new item is unknown, please update or add or ignore that item.&lt;br /&gt;If you want to get to some file out of version control without losing it, please do the following:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Move the file to somewhere safe, not inside your working copy. &lt;/li&gt;&lt;br /&gt;&lt;li&gt;TortoiseSVN → Commit the parent folder. TortoiseSVN will see that the file is missing and you can mark it for deletion from the repository. &lt;/li&gt;&lt;br /&gt;&lt;li&gt;Move the file back to its original location. &lt;/li&gt;&lt;br /&gt;&lt;li&gt;Add the file to the ignore list so you don't get into the same trouble again&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-113824063983323863?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/113824063983323863/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=113824063983323863' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/113824063983323863'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/113824063983323863'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2006/01/setting-up-subversion-in-vsnetwindows.html' title='Setting up Subversion in VS.Net/Windows environment'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-113823828756492099</id><published>2006-01-25T20:18:00.000-05:00</published><updated>2006-01-25T20:18:07.566-05:00</updated><title type='text'>Nhibernate debug note</title><content type='html'>&lt;span style="font-size:85%;"&gt;Nhibernate debug note&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;2, avoid reassign a persistant collection property &lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;     Students = someIList; //avoid this&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;3, refer to log file from the begining of debuging&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt; &lt;/span&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-113823828756492099?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/113823828756492099/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=113823828756492099' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/113823828756492099'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/113823828756492099'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2006/01/nhibernate-debug-note.html' title='Nhibernate debug note'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-113823822677468213</id><published>2006-01-25T20:17:00.000-05:00</published><updated>2006-01-25T20:17:06.773-05:00</updated><title type='text'>NHibernate with Interface Oriented Programming</title><content type='html'>&lt;span style="font-size:85%;"&gt;NHibernate with Interface Oriented Programming&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;Hibernate does not check the property's claimed type. It will always work as long as the object pass to it is assignable to the type NHibernate expect. &lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;This is very important to Interface oriented programming with Hibernate. &lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;The following code works:&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;&amp;lt;class name="Husband, Domain" table="tbHusband"&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;&amp;lt;id name="m_Id" access="field" type="Int32" column="HusbandId" unsaved-value="0"&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;&amp;lt;/class&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;&amp;lt;class name="Wife, Domain" table="tblWife"&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;&amp;lt;many-to-one name="Husband" class="Husband, Domain" column="Husband_HusbandId" unique="true" /&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;&amp;lt;/class&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt; &lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;public class Wife : IWife&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;private IHusband m_Husband; &lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;public IHusband Husband&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;get{return m_Husband;}&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;set{m_Husband = value;}&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt; &lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;public interface IHusband&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;public class IHusband&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;{&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;}&lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt; &lt;/span&gt;&lt;br/&gt;&lt;span style="font-size:85%;"&gt;here is a topic about this pattern&lt;/span&gt;&lt;br/&gt;&lt;a href="http://forum.hibernate.org/viewtopic.php?t=953412&amp;start=0&amp;postdays=0&amp;postorder=asc&amp;highlight"&gt;http://forum.hibernate.org/viewtopic.php?t=953412&amp;start=0&amp;postdays=0&amp;postorder=asc&amp;highlight&lt;/a&gt;&lt;span style="font-size:85%;"&gt;=&lt;/span&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-113823822677468213?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/113823822677468213/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=113823822677468213' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/113823822677468213'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/113823822677468213'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2006/01/nhibernate-with-interface-oriented.html' title='NHibernate with Interface Oriented Programming'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15558048.post-113823817400612919</id><published>2006-01-25T20:16:00.000-05:00</published><updated>2006-01-25T20:16:14.013-05:00</updated><title type='text'>NHibernate Note 1-24-06</title><content type='html'>NHibernate&lt;br/&gt; always use Varchar column for string Discriminator&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15558048-113823817400612919?l=kailuowang.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kailuowang.blogspot.com/feeds/113823817400612919/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15558048&amp;postID=113823817400612919' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/113823817400612919'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15558048/posts/default/113823817400612919'/><link rel='alternate' type='text/html' href='http://kailuowang.blogspot.com/2006/01/nhibernate-note-1-24-06.html' title='NHibernate Note 1-24-06'/><author><name>Kailuo Wang</name><uri>http://www.blogger.com/profile/10695648911502574065</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
