<?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-13841310</id><updated>2012-02-17T04:02:37.317+08:00</updated><category term='i18n'/><category term='development career'/><category term='design patterns'/><category term='javascript'/><category term='java'/><category term='vacation'/><category term='organization'/><category term='isolated classloading'/><category term='process'/><category term='security'/><category term='conspiracy'/><category term='development'/><category term='best practices'/><category term='tourism'/><category term='music'/><category term='methodology'/><category term='team-calendars'/><category term='wtf'/><category term='award'/><category term='concurrency'/><category term='leadership'/><category term='shame'/><category term='atlassian'/><category term='jquery'/><category term='used'/><category term='announcement'/><category term='infrastructure'/><category term='results'/><category term='random interesting stuff'/><category term='random babbles'/><category term='automated testing'/><category term='xss'/><category term='career'/><category term='user interfaces'/><category term='failure'/><category term='architecture'/><category term='codegeist'/><category term='parts'/><category term='sale'/><category term='usability'/><category term='confluence'/><category term='humor'/><title type='text'>Manumission</title><subtitle type='html'>Droplets Of Freedom</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>79</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-13841310.post-2218877234991746764</id><published>2011-12-25T20:16:00.000+08:00</published><updated>2011-12-25T20:16:21.575+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>What do you do for a living?</title><content type='html'>One of the most common questions I have ever been asked and one of the most offensive.&amp;nbsp;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;However, I have to get a point across - it largely depends on the context. Such a question is very rude if asked by a person you just got to know 10 minutes ago, or by someone that hasn't been keeping in touch for a long time. It is perfectly fine, if asked by someone closer, like a parent, one of your mates, your dog, if it could talk, and so forth. The reason for that double standard is because the intention of such a question by someone close to you is clear - it is usually of concern of your well being. Unfortunately, the same can't be said for the former group of people.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To ask someone who you don't know so well about what he does for a living is like asking him which car he is driving. Some people are slightly&amp;nbsp;embarrassed&amp;nbsp;about the car they own. Some people might be selling their cars tomorrow. Some might have really nice cars but do not want to draw any attention to themselves. Some may actually be environmentalists. There are more reasons why people might find it tricky to tell what car they drive. The bottom line is, asking someone you just know about his occupation puts someone in an uncomfortable position. It's invasive and therefore, rude.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Whenever I put forward this idea to people around me, the argument usually goes around the claim that people ask such questions because, as strangers, they do not know what to ask to start a conversation.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Well, my response to that is - what a intolerable pathetic excuse. Such an argument is telling me that it is acceptable to act like a complete arse just so because the chimpanzee that the person is doesn't know what to say next because he ran out of words from his hooting and panting vocabulary.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To have a question that can be asked to any stranger is like having one screwdriver to screw everything up.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A conversation isn't a script. It has to be made up of genuine curiosity and interest. Failing either one, you can't have a meaningful conversation.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-2218877234991746764?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/2218877234991746764/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=2218877234991746764' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/2218877234991746764'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/2218877234991746764'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2011/12/what-do-you-do-for-living.html' title='What do you do for a living?'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-2957061077281004970</id><published>2011-12-24T14:19:00.001+08:00</published><updated>2011-12-24T14:19:10.444+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>"I wouldn't go there if I were you"</title><content type='html'>A phrase most of us are, perhaps, familiar with. Certainly to me, it is. Whenever I suggested to go to a place, or in a broader sense, to do something that is perceived to be risky, people generally paint a bleak picture in response. To say, "I wouldn't go there if I were you" suggests to me that I should consider the person's recommendation, who have just heard of an plan of mine that I have been thinking about for a significant amount of time, and cease it.&lt;br /&gt;&lt;br /&gt;It could be a logical thing to consider if such a recommendation is backed by actual experience. Unfortunately, most of the time, a response like that comes from people who haven't actually been or done what was initially suggested. I believe, sincerely, that they did not respond in that way to hinder my dreams, and actually, I quite appreciate the concern.&amp;nbsp;What I'm upset about, is that such a comment can be made with utter lack of responsibility. Whatever that gave them the credentials to make a recommendation like that is well beyond me. However, when given enough thought, a negative opinion, purely, can become the prime cause of a given up aspiration.&lt;br /&gt;&lt;br /&gt;This happens more with the topic of traveling destinations. Try telling your family that you'd like to travel to Africa and stay there for a few months. Try telling your family that you're planning to do a massive cross-country road trip.&lt;br /&gt;&lt;br /&gt;An experience that was shared with me explains this a little better. A friend of mine, recently, spoke to a family member of hers at a grand family dinner. Such dinners are joined by family members, wherever they may be and willing to come. It could be imagined as if it was a &lt;a href="http://nuhk.blogspot.com/2011/12/chinese-wedding-dinner.html"&gt;Chinese wedding dinner&lt;/a&gt;&amp;nbsp;- maybe more meaningful and consequently slightly less wasteful.&lt;br /&gt;&lt;br /&gt;Travelling, is one of the popular topics for small talk in such an occasion. So a relative of my friend, was conversing with her about her travels. In just 3 months, my friend had travelled to Indonesia twice - to Bali and Lake Toba. Those are popular destinations. So popular in fact, they could even be said as touristy. However, in the mention of Indonesia, my friend told me her relative was surprised. It was as if Indonesia was place where they put travelers into giant pots of boiling water.&lt;br /&gt;&lt;br /&gt;Surprised by such reaction, my friend asked her relative if he has been there before. It was, I guess, interesting to know, what could've caused someone to have an opinion like that. My friend's experience with Bali and Lake Toba was very pleasant. In fact, she is hoping to travel there again soon. When the relative was asked by my friend if he has been there before, the answer was "no".&lt;br /&gt;&lt;br /&gt;So, what if it is true, backed by experience, that the place is indeed dangerous? How would a comment like that help? To demonstrate this point, I'd use a road trip as an example.&lt;br /&gt;&lt;br /&gt;Nobody plans a road trip to the park just down the road from their homes - familiar and safe. Road trips are adventures. A road trip is a way to experience the elements and spirits of the places that are travelled through. People don't take road trips because it is the fastest way to get to another place. A road trip is mainly about the journey itself, not what would be happening in the end. In that sense, the risks and the struggle would, in the end, be the most memorable parts of the journey. If any of those components are taken out, the journey becomes pointless.&lt;br /&gt;&lt;br /&gt;So if you're planning a road trip, it's really hard to understand the purpose of a remark by someone else, not on the trip, saying he or she wouldn't do it if he or she was you, especially if that someone hasn't actually done one before. The comment is complete rubbish. Nonsense. If it proves anything, it's the inept of the person who made the remark at understanding an adventure.&lt;br /&gt;&lt;br /&gt;I'd believe in every conversation, there is something to be gained. Even if it was an exchange of utterly vulgar, non-sensical jokes, there is a good laugh to have at the end of it. However, with a negative, demoralizing, baseless remark, it's hard to see what it is there to be gained.&lt;br /&gt;&lt;br /&gt;Instead, being positive, or encouraging, is the least that could be done. If there is experience to be shared, share it in the form of things that needs to be considered before taking on a risky activity.&lt;br /&gt;&lt;br /&gt;Help, don't hinder.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-2957061077281004970?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/2957061077281004970/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=2957061077281004970' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/2957061077281004970'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/2957061077281004970'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2011/12/i-wouldnt-go-there-if-i-were-you.html' title='&quot;I wouldn&apos;t go there if I were you&quot;'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-1618712174721575222</id><published>2011-12-20T04:04:00.001+08:00</published><updated>2011-12-20T04:04:04.086+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>Chinese Wedding Dinner</title><content type='html'>&lt;br /&gt;The last button of a perfectly ironed shirt fastened. It has been a long time since it was last worn. So last checks on the shirt for unwanted marks and stains are performed. Everything looks good. It's 6pm, an hour away from the banquet which the invite to was accepted more than 9 months ago. Yes, it's a Chinese wedding dinner. Yes, it will be quite grand. There will be more than 300 people attending. Everyone will be at their best. It should be quite fun.&lt;br /&gt;&lt;br /&gt;After a 40 minutes drive, the restaurant, which is, by the way, very famous for traditional Chinese cuisine, was in sight. It must've been booked by the new couple a year or more ago. There's a car park beside it. Unfortunately, there wasn't any space left. While searching for a lot, ladies could been seen in high heels galloping slightly to try to make it before the appointed time - 7pm.&lt;br /&gt;&lt;br /&gt;The tension building up. It's getting late. 10 minutes into the search, there still isn't a lot yet. On the 13th minute - fortune smiled. There was just enough space between two cars that were not parked squarely in the box. The space taken without hesitation. The rush is over. The party starting. Perfect, apart from one problem.&lt;br /&gt;&lt;br /&gt;I don't know the newly weds. Not bride, not the groom, not even their families. Why am I there?&lt;br /&gt;&lt;br /&gt;Well, I did know the groom, but I don't know him now. He was in the same class as I was when I was 12 years old. Since we graduated from elementary school, we haven't been keeping in touch. In essence, it's almost 2 decades since I last spoke to him. How could I say I know the groom?&lt;br /&gt;&lt;br /&gt;This is one of problems, amongst many, that I see in Chinese wedding dinners. If Facebook was designed as a database linking a person to everyone he ever knew, the inspiration must have had come from Chinese wedding dinners.&lt;br /&gt;&lt;br /&gt;A Chinese wedding dinner is like a party that you're invited to crash into. It's a party that lasts 9 courses long, where you are seated with a whole bunch of strangers, eating the same food that you had in some other wedding dinners. Each course in a Chinese 9-course dinner is shared. It is served on an enormous plate, placed on a rotating platform at the center of the round table. While the setup works for most courses, it simply is a unsightly inconvenience with some. For instance, fish.&lt;br /&gt;&lt;br /&gt;Most restaurants just serve fish as it comes out of the kitchen - cooked, but uncut, unopened and with a sizable spoon (yes, spoon) for guests to gore the fish with. After half a round on the table, the fish could only be described as having exploded and vultures had their turns to gnaw at it.&lt;br /&gt;&lt;br /&gt;Between the courses, a seemingly friendly stranger, guaranteed, would try to have a conversation with you. I think that is nice. Unfortunately, most conversations start with "What do work as?". It might be way I was brought up, but if my upbringing was in order, that is the rudest question that a stranger could ask. Fortunately, it's easy to shrug off questions like that but at the cost of avoiding the person the entire evening.&lt;br /&gt;&lt;br /&gt;One of the common courses in a 9-course meal is an enormous plate of giant prawns. Now, that sounds like a real delicacy, doesn't it? Probably, if the prawns' shell are not intact. There are two parts to the reason why that is a problem. First, chopsticks are used. Using chopsticks to peel a prawn is like wearing boxing gloves perform surgery. Second, since Chinese wedding dinners are usually very grand, people dress up to it. So naturally, especially the ladies, would not peel the prawns themselves. Not a problem because there are gentlemen at the table, correct? Rightfully so. However, because the gentlemen get so busy peeling the prawns for the ladies, they don't get enough time to do it for themselves. By the time they're finished with the peeling for the ladies, the next course would've been served.&lt;br /&gt;&lt;br /&gt;I haven't yet written the biggest problem with Chinese wedding dinners. The biggest problem, in my opinion, with Chinese wedding dinners (or dinners in general) is shark's fin. Most Chinese wedding dinners include shark's fin as one of the courses. While Chinese see shark's fin as a representation of status (because they are expensive), in reality, it is really a bowl of animal cruelty.&lt;br /&gt;&lt;br /&gt;Some might argue that the fins served in Chinese wedding dinners these days are not real fins. So shark's fin in wedding dinners isn't really a bowl of cruelty. However, I think it's still a problem when the notion of enjoying the end result of finning is demonstrated.&lt;br /&gt;&lt;br /&gt;To me, a Chinese wedding dinner is an awkward, offending, inconvenient waste of food and part of animal cruelty.&lt;br /&gt;&lt;br /&gt;I think Chinese wedding dinner has lost its meaning to commercialization. I don't know the way to restore it. I'm not historian. Even then, I don't know being historically or traditionally correct would restore the meaning. However, the immediate thing that we could do is to first tell restaurants that we do not want shark's fin served. Along with that, we should ask them to reduce the portions. Food that get wasted because of inconvenience should be substituted by something more convenient.&lt;br /&gt;&lt;br /&gt;Finally, to couples that are getting wedded, please invite only people who you know and truly care about.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-1618712174721575222?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/1618712174721575222/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=1618712174721575222' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/1618712174721575222'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/1618712174721575222'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2011/12/chinese-wedding-dinner.html' title='Chinese Wedding Dinner'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-7868171752490041246</id><published>2011-12-19T23:36:00.003+08:00</published><updated>2011-12-19T23:40:40.476+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>"You're so different at work"</title><content type='html'>&lt;br /&gt;I like having conversations. I do. Sincerely. Really.&lt;br /&gt;&lt;br /&gt;At work, I enjoy having brief chats with people whenever I could afford it. It helps me to understand the people who work in the same office space as I do. I find most of the conversations quite interesting.&lt;br /&gt;&lt;br /&gt;At work, I would get into this zone that sits in my mind where I am completely by myself and there is nothing other than a task to get done. It's great state to be in because locking out everything other than just the task at hand increases productivity massively. The only bad thing about this zone is that if done so frequent and long enough, you would end up alienating yourself from the people in the office. In other words, you'd experience a disconnect from the rest of the organization.&lt;br /&gt;&lt;br /&gt;So I find the need and the interest to have casual chats with people in the office. It's just as simple and pure as that. However, casual chats in the office do come at a high price.&lt;br /&gt;&lt;br /&gt;Whenever a chat takes place, it takes time as well. Every work day is assumed to be 8 hours worth of work. Ideally, you'd finish the 8 hours sharp on the clock and you would have done 8 hours worth of productivity. When a chat takes time, it cuts the productivity output. Of course, the loss of productivity could be made up. There are two ways - compress the workload or to expand the time initially allocated for the same amount of output.&lt;br /&gt;&lt;br /&gt;In other words, casual chatting in the office isn't free. It takes time and energy, amongst many other things.&lt;br /&gt;&lt;br /&gt;So you can imagine, how tiring it could be at the end of a work day if the same amount of productivity has been produced and time spent on just having conversations. If what I've written so far sounds familiar to you, someone, at some point, would've made the remark below about you.&lt;br /&gt;&lt;br /&gt;"You're so different at work"&lt;br /&gt;&lt;br /&gt;I go home exhausted. The last thing I want is another conversation or another thing to do. I don't suppose everyone feels the same way that I do, but I don't think what I'm writing about now is something unfamiliar to most people. When I get home, I just want to sit in front of my tele with a pack of crisps and just mindlessly crunch it all away to some television program that I completely disregard. It's a fun, maybe slightly eccentric, way to unwind.&lt;br /&gt;&lt;br /&gt;But unfortunately, nothing ideal ever happens. My tele would most of the time be occupied by someone else in the family. My crisps would've been wolfed down and I would probably have a few more tasks to do. In the worst days, some of those tasks may include having conversations.&lt;br /&gt;&lt;br /&gt;I'm not talking about a scenario where someone puts you in - like someone telling you to have a conversation with some other person. It's a task you get when you sit in a room that is filled with at least one other person. I've tried just ignoring that person, hoping that I wouldn't somehow get into a conversation, but from experience, it doesn't work. The other person is probably thinking the same thing. It then becomes an endurance race - how long before someone feels the need to break the silence with a stupid question.&lt;br /&gt;&lt;br /&gt;And then, someone does. If that someone isn't you, you'd probably get a question like "have you had your dinner?"&lt;br /&gt;&lt;br /&gt;"No, I haven't. What are you going to do about it?"&lt;br /&gt;&lt;br /&gt;A fight entails, I'm sure of it. Congratulations, you've just created a sucky day for two people.&lt;br /&gt;&lt;br /&gt;The thing is, I don't think that we, as individuals, are prepared to converse any minute of the day. I think we all need our space. We all need our time alone. Well, at least, I do.&lt;br /&gt;&lt;br /&gt;So I'm really excited that I'm about to go on short a trip, accompanied only by a book. Where I am going is probably 6-8 hours drive away, based on the route I'm taking. It will take quite some effort, even just to stay on the road! It's going to totally boring, utterly uninteractive. But it's precisely what I need and I know I will enjoy it a lot.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-7868171752490041246?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/7868171752490041246/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=7868171752490041246' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7868171752490041246'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7868171752490041246'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2011/12/youre-so-different-at-work.html' title='&quot;You&apos;re so different at work&quot;'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-8318204511787423465</id><published>2011-12-14T22:54:00.001+08:00</published><updated>2011-12-14T22:54:52.271+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='process'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><title type='text'>Proper software maintenance and communicating about it</title><content type='html'>I bought a TV about week ago. It's been a number of years since my last. For a geek wannabe (me), going through the features of a standard modern TV surprised me. The software in it is actually quite comprehensive, though to others, bloated. It's quite impressive, but naturally, I knew there would be bugs. I just hoped I don't get affected by it.&lt;br /&gt;&lt;br /&gt;Along with a new TV, I also bought a media player. The TV wouldn't play all the movies in my computer.&lt;br /&gt;&lt;br /&gt;So I have a TV and a media player connected via a HDMI cable. Quite a simple setup, really. What could go wrong?&lt;br /&gt;&lt;br /&gt;A few days after the TV was delivered to my apartment, I found a problem with it. The sound would drop &amp;nbsp;for 1-5 seconds at random - I'd get a few drops in every 15 minutes. A TV setup with a bug - the thing that I hoped I wouldn't come across. I never did, actually, until this new set.&lt;br /&gt;&lt;br /&gt;After some basic troubleshooting and not finding the fix, I went to Google. In the first 10 minutes of my search, I found something related. Some people in a forum were talking about the same problem. It was an old thread - so there was a lot to go through. At the end of it, I knew I needed to find firmware updates for both my media player and TV. So I started with my media player first.&lt;br /&gt;&lt;br /&gt;Like most product web sites, new stuff and promotions always get the strongest emphasis. If it wasn't for my experience building web applications, it would've been tricky to find where the firmware updates. Eventually, though, I found them, for both my media player and TV.&lt;br /&gt;&lt;br /&gt;I went through the release notes to see if they've fixed my problem. Unfortunately, no mention. However, they did mention several improvements. So I decided that it was worth it to upgrade. Like most firmware upgrades, the instructions are pretty complex and intimidating (I'll explain). It goes something like:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Download xxx_yyy_zzz.zip and extract it.&lt;/li&gt;&lt;li&gt;Copy install.yyy to the root of your USB drive.&lt;/li&gt;&lt;li&gt;Turn off your media player - unplug from the power socket it if you have to.&lt;/li&gt;&lt;li&gt;Plug the USB stick to your media player&lt;/li&gt;&lt;li&gt;Hold the power button on your media player and then turn it on.&lt;/li&gt;&lt;li&gt;Release the button and the media player will update itself. When it is updating - &lt;b&gt;do not turn it off under any circumstances, not even if the sun is going to explode&amp;nbsp;&lt;/b&gt;(well, more or less).&amp;nbsp;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Can you imagine your grandmother who's staying 6 time zones away trying to fix a sound problem with her TV? Just the warning alone might scare her off!&lt;br /&gt;&lt;br /&gt;Eventually, the media player firmware was updated. So I tested to see if the firmware did fix the problem. I knew the release notes didn't say anything about that, but from experience, most release notes are not 100% accurate, for many reasons. Anyways, problem was still there.&lt;br /&gt;&lt;br /&gt;It was the TV's turn to get its firmware updated. I went through the same process as I did with the media player. There was mention of the problem being fixed! Hooray! Unfortunately, it was for a firmware version older whatever that's in the TV.&lt;br /&gt;&lt;br /&gt;However, knowing release notes are not 100%, I thought it may contain unmentioned regression fixes. That's how I decided that the firmware upgrade was worth a try. The steps were similar, and with similar warnings. So after 10 minutes, the update was complete.&lt;br /&gt;&lt;br /&gt;I tested for the problem again and it seemed fixed! I didn't get any sound drops 60 minutes into my movie!&lt;br /&gt;&lt;br /&gt;Then I thought for a while - what if I'm someone much less familiar with these "technical" things. Would I have fixed my own problem? The answer, to me, is an obvious "no".&lt;br /&gt;&lt;br /&gt;The problem isn't with the products' testing. It isn't with the firmware development. Bugs (software problems) will occur. As long as they get fixed in a timely manner, it's fine.&lt;br /&gt;&lt;br /&gt;The problem is with the fixes not being emphasized enough. You see, it was so easy to find people talking about the problems with the TV setup. Unfortunately, it was not as easy to find the right hint to fixing the problem (the firmware update). It was actually quite easy to miss, as the forum thread contains many responses (old thread).&lt;br /&gt;&lt;br /&gt;When I followed the hint, product web sites made it hard for me to find their fixes. When I found their fixes, their release notes weren't accurate. The upgrade procedure felt like a surgery.&lt;br /&gt;&lt;br /&gt;When people can't get their problems fixed, they talk about it. Either on Facebook, with their friends over beer, emails, etc. Word just get around. That can't be good for the product manufacturer, can it?&lt;br /&gt;&lt;br /&gt;Worse still, to a careful shopper, hearing people talking about the problems of the product without fixes could just turn him or her around. Lost sales!&lt;br /&gt;&lt;br /&gt;I just think product manufacturers should be more proactive with their fixes. For instance, maybe notifying customers of updates when they are released. Tell them what has been fixed, even regressions - keep accurate release notes.&lt;br /&gt;&lt;br /&gt;Hopefully, that will make people talk about the problems with the products &lt;b&gt;and the solutions&lt;/b&gt;&amp;nbsp;rather than just the problems themselves. Things like that give potential customers the confidence in the products.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-8318204511787423465?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/8318204511787423465/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=8318204511787423465' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8318204511787423465'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8318204511787423465'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2011/12/proper-software-maintenance-and.html' title='Proper software maintenance and communicating about it'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-8676995851142103770</id><published>2011-11-20T21:18:00.001+08:00</published><updated>2011-11-20T22:59:22.917+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>Bon Voyage</title><content type='html'>For a long time now, I've been talking about doing a road trip, maybe going north to neighboring countries or something. The thought of going on a journey was seeded much earlier than the road trip. Even before I got my driver's license, I have always been fascinated by going to places that I haven't been before.&lt;br /&gt;&lt;br /&gt;I'm not talking about taking a flight to some place, take some pictures then mark that place off from the list of places to visit. I'm talking about a journey - through unfamiliar roads and places.&lt;br /&gt;&lt;br /&gt;When I was younger, I had a bicycle that I rode to anywhere. I would ride it to get some breakfast, or just to have a morning ride around the areas near my place, or just bumbling along down some back alleys. At that age, as a kid who just got a bike, there were many paths and roads that I came across that I didn't normally notice when seated at the back of a car. Whenever I came across a peculiar path like that, I took a detour and explored it.&lt;br /&gt;&lt;br /&gt;The explorations paid off - I found great hideouts spots (though, not that I need them) and shortcuts, amongst many things, although a lot of them were some kind of abandoned path that led to nowhere.&lt;br /&gt;&lt;br /&gt;What were at the end of the paths I explored weren't that important. At least, not as important as the journey themselves. Y'know, being scared and still exploring and then finally relieved that the path has ended and knowing exactly where I am. It's like going into a haunted house and coming out alright.&lt;br /&gt;&lt;br /&gt;Then I got bored of exploring the same surroundings. I've gotten to know my area like the back of my hand. So I progressed to wanting to use another form of transport for my "explorations". And then, I got my driver's license.&lt;br /&gt;&lt;br /&gt;The car worked really well. It allowed me to explore much further. And I did. Every now and then, I'd mark a spot on the map and see what are the routes I could take to get to it. It's easy, nowadays, with Google Maps. I'd skip all the highways and get on the longest trunk roads.&lt;br /&gt;&lt;br /&gt;Maybe it's because of the city boy I am, but trunk roads that I haven't been on always look like roads that go nowhere. They're a lot more dangerous - narrow, curvy, blind corners, no overtake lanes, etc. However, they are usually more exciting. For one, the scenery is almost always better - water dams, really close to the hills and scattered empty (void of customers) restaurants serving Tom Yam rice (usually).&lt;br /&gt;&lt;br /&gt;I really like the spontaneous feeling of driving along a road that I haven't been on before and coming across a restaurant. If it looks alright, I'll just pull up right in front of the restaurant. Then, I'll choose a table close to my car and just chill out for an hour or two before moving on to the destination.&lt;br /&gt;&lt;br /&gt;If I'm hungry while driving on a trunk road, I'd look for an empty enough restaurant. It's weird, but I really enjoy that. Maybe it's because of the feeling of timelessness - the&amp;nbsp;spontaneous&amp;nbsp;nature of it, with time to spend, maybe, that's what's really in it for me... but I never bothered to find out.&lt;br /&gt;&lt;br /&gt;So after a few years of indulging myself in this weird "hobby", I thought to myself, why not make it a really long journey - across countries, cultures, food and climate that I'm not familiar with. It would be the ultimate (for now ;)) exploration for me. It would be the mother of all bike rides, it would the mother of all drives on trunk roads.&lt;br /&gt;&lt;br /&gt;It would be simply fantastic.&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-8676995851142103770?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/8676995851142103770/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=8676995851142103770' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8676995851142103770'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8676995851142103770'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2011/11/bon-voyage.html' title='Bon Voyage'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-9118985782550937903</id><published>2011-10-12T20:26:00.001+08:00</published><updated>2011-10-12T20:26:19.832+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>Back to 1984</title><content type='html'>"You are defined by what you do" - an idea that most of us have no problems accepting. It should be so, because it is right, isn't it? A hero by actions, a saint by deeds. There is no other way.&lt;br /&gt;&lt;br /&gt;But really?&lt;br /&gt;&lt;br /&gt;I think a person is defined by what they think, not really by what they do. For instance, a person thinking about committing a crime is already a bad person. Sure enough, as long as he does not commit the crime itself, he is still as innocent as a newborn... but what kind of a person is he if he just thinks about things that hurt people?&lt;br /&gt;&lt;br /&gt;How can a person be considered a good person if the only thing that prevents him from doing the bad things is the impending punishment once convicted?&lt;br /&gt;&lt;br /&gt;I think good, if it exists, is something that is pure and simple. The debates of what is acceptable by society at a particular period is not relevant. Public perspective changes all the time, but not conscience. I think it works on a very simple logic - good is something that we welcome and bad is something we want to stay away from.&lt;br /&gt;&lt;br /&gt;Thus, I don't believe anyone sane would want to be a victim of a criminal act. I don't believe anyone sane would want to be a victim in a criminal plot, no matter how distant it is. If that is granted, how could it &lt;i&gt;not&lt;/i&gt; be wrong to think about doing things to others the things we wouldn't want happen to ourselves?&lt;br /&gt;&lt;br /&gt;"Don't do unto others what you don't want others do unto you"&lt;br /&gt;&lt;br /&gt;No one needs to be watching.&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-9118985782550937903?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/9118985782550937903/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=9118985782550937903' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/9118985782550937903'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/9118985782550937903'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2011/10/back-to-1984.html' title='Back to 1984'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-1053494843684496363</id><published>2011-08-13T13:33:00.002+08:00</published><updated>2011-08-13T19:29:34.221+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='team-calendars'/><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='confluence'/><category scheme='http://www.blogger.com/atom/ns#' term='atlassian'/><title type='text'>Engineering Team Calendars for Confluence</title><content type='html'>It's been about two months since our launch and I'm really thankful for all the interest in it so far! It's great!&lt;br /&gt;&lt;br /&gt;We've started &lt;a href="http://nuhk.blogspot.com/2011/06/lessons-learned-building-team-calendars.html"&gt;developing Team Calendars a year plus ago&lt;/a&gt;. The team was primarily made up of a product manager, a designer, and myself, the developer. There were people helping out occasionally - on QA, documentation and of course, marketing. Since 1.0, we've had a couple of rotations due to people going on leave and work load, but the roles that made up the primary team haven't changed too much since 1.0.&lt;br /&gt;&lt;br /&gt;We're still a very nimble team!&lt;br /&gt;&lt;br /&gt;Having spent the time that I've spent with Team Calendars, I thought it would only be less selfish of me if I shared some of the &lt;i&gt;techinical&lt;/i&gt;&amp;nbsp;things I've learnt. So I'm going to share the things I've learnt in a series of posts.&lt;br /&gt;&lt;br /&gt;The two main components of Team Calendars are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The UI, built with &lt;a href="http://arshaw.com/fullcalendar/"&gt;FullCalendar&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;The business logic manipulating calendar data via&amp;nbsp;&lt;a href="http://wiki.modularity.net.au/ical4j/index.php?title=Main_Page"&gt;ical4j&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;div&gt;In this post, I'm going to talk about why we went ahead with FullCalendar.&lt;/div&gt;&lt;br /&gt;&lt;div&gt;A lot of the technical decisions made for Team Calendars were based what we've learnt about the older &lt;a href="https://plugins.atlassian.com/plugin/details/153"&gt;Open Source Calendar Plugin&lt;/a&gt; for Confluence. It certainly has some issues, however, it (still) is one of the most popular plugins for Confluence. So, being popular, there is a lot of feedback on it. So what we did, was the use some of the feedback when we were building Team Calendars.&lt;br /&gt;&lt;br /&gt;One of the main issues with the Open Source Calendar Plugin was that its UI is pretty archaic by standards today. For instance, &lt;a href="https://studio.plugins.atlassian.com/browse/CAL-177"&gt;multi-day events do not span across appropriate time slots&lt;/a&gt;. The main challenge with changing that, to what I understand, is that the whole calendar was rendered by the server (Confluence).&lt;br /&gt;&lt;br /&gt;The easiest way to render an event across several time slots in a view (e.g a day cell in the month view) for the browser is to position the event element absolutely on a relatively positioned container. To position an element absolutely means to position an element with x-y coords, generally. To be able to calculate those coordinates, the dimensions of the calendar (including the events) need to known.&lt;br /&gt;&lt;br /&gt;Unfortunately, it's very tricky to calculate HTML element dimensions in the server. The dimension depends on many things - line height, font size, border width, padding, margin and even how it overflows. Even if the dimensions are known, it's still pretty tricky to position all those elements correctly in the HTML being generated.&lt;br /&gt;&lt;br /&gt;Why? Imagine yourself as the calendar renderer. You're going through a list of events to calculate positions of, and after calculation, you append it to the HTML markup (text). What if, half way down the list, you need to adjust some of the positions in some of the events that have already been rendered to HTML?&lt;br /&gt;&lt;br /&gt;Of course, you could retrieve &lt;i&gt;all&lt;/i&gt;&amp;nbsp;the events you need to render so you could calculate all their positions before appending them to the final HTML. However, that creates another problem. Events can come from an external source, and for those events, Team Calendars need to go to that site, with certain network timeouts, to retrieve them.&lt;br /&gt;&lt;br /&gt;The problem is when we're retrieving events, internal or external, and generating a view of them in &lt;i&gt;one&lt;/i&gt; HTTP request. That's a lot to do. The risk in that is that the problems aren't being solved in a divide by conquer fashion, so it will take time. The end result could possibly be the perception by the users that the calendar isn't responsive at all. And if it does come to that, y'know, they'd be right.&lt;br /&gt;&lt;br /&gt;The inefficiency of rendering a calendar in the server is obvious. The implications of the problem would be further amplified by the fact that we need to allow users to toggle events on and off of a sub-calendar. If we rendered everything in the server, each toggle would mean a repeat of the expensive operation.&lt;br /&gt;&lt;br /&gt;So clearly, rendering a calendar in the server is not the right way to go. We'd solve a lot of problems by just doing it in the browser.&lt;br /&gt;&lt;br /&gt;But of all JS calendars, why FullCalendar? Well, FullCalendar supports the model that we want with Team Calendars. It looks sexy and it's quite mature, and what I mean by mature is that during the whole development of Team Calendars till present, I haven't encountered a major bug that stops me from adding features to Team Calendars.&lt;br /&gt;&lt;br /&gt;Furthermore, if you look at &lt;a href="http://arshaw.com/fullcalendar/docs/"&gt;its documentation&lt;/a&gt;, all the integration hooks that I think a developer would need to implement any generic calendar with it are provided.&lt;br /&gt;&lt;br /&gt;I take my hat off for &lt;a href="http://arshaw.com/"&gt;Adam Shaw&lt;/a&gt;, who authored FullCalendar. Fantastic work!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-1053494843684496363?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/1053494843684496363/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=1053494843684496363' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/1053494843684496363'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/1053494843684496363'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2011/08/engineering-team-calendars-for.html' title='Engineering Team Calendars for Confluence'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-5727468840769974744</id><published>2011-06-10T10:24:00.001+08:00</published><updated>2011-06-11T12:00:50.157+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='team-calendars'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='confluence'/><category scheme='http://www.blogger.com/atom/ns#' term='atlassian'/><title type='text'>Lessons learned building Team Calendars for Confluence</title><content type='html'>The lack of updates to this blog is justified this time - I cannot be blamed :)&lt;br /&gt;&lt;br /&gt;For quite a while now, I have been busy building the &lt;i&gt;first&lt;/i&gt; version of &lt;a href="http://www.atlassian.com/en/software/confluence-team-calendars"&gt;Team Calendars for Confluence&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;We launched Team Calendars officially on June 7th, the second day of &lt;a href="http://summit.atlassian.com/"&gt;Atlassian Summit 2011&lt;/a&gt;. Since then, we've received a lot of positive feedback and ideas for improvements, and boy, am I glad we're seeing such good responses! It's probably the best validation for the work we've done yet :)&lt;br /&gt;&lt;br /&gt;The development of Team Calendars started a long time ago. In that period, we've built about 50 milestones, about 3 different models and many major UI revisions. What the hell? So many versions? Yes :)&lt;br /&gt;&lt;br /&gt;So I thought I'd share 3 things I've learned from the process.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1. When building a new product, specifications are a guideline, feedback is the mandate&lt;/b&gt;&lt;br /&gt;What's funny was that we've started off with a spec. The first major milestones were developed according to it. At the end of that, I thought we had a product. We could ship! Yay!&lt;br /&gt;&lt;br /&gt;However, we felt that it didn't have the pizazz we were looking for. Although specifications were met within reasonable timeline (and I think it is fair to say that the experience that we had developing the first prototypes is probably a consultant's wet dream), we had felt like we didn't have a product with "soul".&lt;br /&gt;&lt;br /&gt;Some of us started to ask how we could add more value to what we already have. There wasn't an obvious all-agreeing answer then. It was as if we were dumbfounded... but then we got some feedback internally.&lt;br /&gt;&lt;br /&gt;The responses we received were encouraging (although scary, because it looked like we were heading towards a different approach altogether!).&lt;br /&gt;&lt;br /&gt;However, it was interesting to hear the feedback. As I went through the collated feedback, I kept on thinking "How did I never thought about that? Man, that's a great idea!".&lt;br /&gt;&lt;br /&gt;I guess the lesson is pretty simple - the users know more than us, who built the product according to the specifications.&lt;br /&gt;&lt;br /&gt;Although the specifications were built based on feedback, there are a lot of differences between initial feedback and feedback that we got when we had a working prototype.&lt;br /&gt;&lt;br /&gt;Feedback is not something that only needs to be asked for once (for specs). Feedback is something that needs to be asked for throughout.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. Build prototypes frequently&lt;/b&gt;&lt;br /&gt;With Team Calendars, we went with a weekly frequency. Every week, we would have a new version to play around with. It's easy to argue that in one week, not much could change, and that is largely true, if I look at it from the development perspective (y'know, changes to about 20 source files, 200 lines of code change, etc).&lt;br /&gt;&lt;br /&gt;However, my experience with weekly builds completely surprised me. The usual small changes in the code generated a lot of feedback. For instance, turning off a flag that enables event adding/editing in the Team Calendars macro confused/annoyed many users.&lt;br /&gt;&lt;br /&gt;We thought the macro would be usually used to show events on a page, so we disabled event adding/editing. However, we were proven wrong when many of our early-users asked how to add/edit events on a page. When we learned from them why they'd like that, it all made sense :)&lt;br /&gt;&lt;br /&gt;After we allowed event adding/editing in one of the following weeks, we received some good feedback how the UI should look like to make it easier, and we did :) And we're glad we did.&lt;br /&gt;&lt;br /&gt;The bottomline is that if it wasn't for prototypes, we probably wouldn't have ended up giving the ability to add/edit events from the macro. I thought it was reasonable for the macro to be read only, and I would think that many would agree when that's &lt;i&gt;only on&lt;/i&gt; the specifications.&lt;br /&gt;&lt;br /&gt;Feedback generated by playing around with the prototypes are more realistic.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3. Do not over-engineer&lt;/b&gt;&lt;br /&gt;When building a product from scratch, for a technical person, it is very tempting to add more flexibility to the product that what is actually required. For instance, giving the product extension points when it is still being built.&lt;br /&gt;&lt;br /&gt;Adding flexibility or fanciness when I haven't had a product, to me, is a bit like designing my first car that can be mounted with any type of engine. Sure enough, it would be quite an engineering accomplishment (can you imagine if the car can be mounted with gravity propelled engine with just 4 bolting points?). :)&lt;br /&gt;&lt;br /&gt;Over-engineering increases costs for almost no immediate benefit. When pushing a product out the the market, what is most important is for it to catch on to users/customers and to minimize the time to market of it. That isn't to say quality can be compromised for those reasons, it should never. Over-engineering usually doesn't increase quality. In fact, in my experience, over-engineering has negative impact to the quality due to the complexity it adds.&lt;br /&gt;&lt;br /&gt;Not over-engineering is especially important when developing 1.0 of a product. When moving towards 1.0, there are a lot of unknowns. To reiterate, we built about 3 different models of Team Calendars. When we moved from one model to another, we had to scrap and rebuild a lot of its code. &lt;br /&gt;&lt;br /&gt;Keep focused and keep it simple. I think that will give it the ultimate flexibility of becoming something that users would find useful.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-5727468840769974744?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/5727468840769974744/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=5727468840769974744' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/5727468840769974744'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/5727468840769974744'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2011/06/lessons-learned-building-team-calendars.html' title='Lessons learned building Team Calendars for Confluence'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-2902050269073860458</id><published>2011-03-07T22:02:00.001+08:00</published><updated>2011-03-09T19:42:20.539+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sale'/><category scheme='http://www.blogger.com/atom/ns#' term='used'/><title type='text'>I'm selling my MacBook Pro</title><content type='html'>I have a &lt;a href="http://support.apple.com/kb/sp499"&gt;MacBook Pro 15" (2.4Ghz Core 2 Duo)&lt;/a&gt; that &amp;nbsp;I'd like to sell for RM 3200. In addition to the standard package, I will include:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Snow Leopard install disc&lt;/li&gt;&lt;li&gt;4GB RAM&lt;/li&gt;&lt;li&gt;AppleCare lasting for another 1+ years&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;The original box is still with me. If you're interested, please contact me at &lt;i&gt;david dot cwy at gmail dot com&lt;/i&gt; or call me at my cell at &lt;i&gt;+60 (17) 2946423&lt;/i&gt; (David Chui).&lt;br /&gt;&lt;br /&gt;Related &lt;a href="http://forum.lowyat.net/index.php?act=ST&amp;amp;f=98&amp;amp;t=1782456&amp;amp;st=0#entry40556081"&gt;post at lowyat.net&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update&lt;/b&gt;: I've sold my Mac&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-2902050269073860458?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/2902050269073860458/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=2902050269073860458' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/2902050269073860458'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/2902050269073860458'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2011/03/im-selling-my-macbook-pro.html' title='I&apos;m selling my MacBook Pro'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-4762160102485685723</id><published>2010-10-24T19:08:00.000+08:00</published><updated>2010-10-24T19:08:03.229+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='development'/><title type='text'>Click event not fired if background is transparent (Internet Explorer 7)</title><content type='html'>I think this needs some mention - I could find related topics with Google, but I couldn't find posts that mention about the problem I had to recently deal with in IE7 specifically - clicking the inside of an empty box with a transparent background doesn't actually fire the click event.&lt;br /&gt;&lt;br /&gt;For some requirement, I had to make an empty link (a tag) look like a box. The element looks like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;a class="colored-box" href="#"&amp;gt;&amp;lt;/a&amp;gt;&lt;/pre&gt;&lt;br /&gt;It has two modes - enabled and disabled. In the enabled mode, it's just a box with a specific opaque background color. In the disabled mode, it will not have any background color, but its border color will be of the background color of the enabled mode.&lt;br /&gt;&lt;br /&gt;Simple?&lt;br /&gt;&lt;br /&gt;This is how the CSS looks like:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;.colored-box {&lt;br /&gt;&amp;nbsp;&amp;nbsp;background-color: #cc3333;&lt;br /&gt;&amp;nbsp;&amp;nbsp;border: 1px solid #cc3333;&lt;br /&gt;&amp;nbsp;&amp;nbsp;/* More styles */&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;.foo .bar .colored-box-disabled {&lt;br /&gt;&amp;nbsp;&amp;nbsp;background-color: transparent;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;So, in jQuery speak, this is how my code looks like:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;jQuery(function($) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;$(".colored-box").click(function() {&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; var $coloredBox = $(this);&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; if ($coloredBox.hasClass("colored-box-disabled")) {&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;$coloredBox.removeClass("colored-box-disabled"); // Enabled mode&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; } else {&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;$coloredBox.addClass("colored-box-disabled"); // Disabled mode&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;return false;&lt;br /&gt;&amp;nbsp;&amp;nbsp;});&lt;br /&gt;});&lt;/pre&gt;Everything was fine and dandy with Safari, FireFox, IE8... and a strange thing happened when I tested with IE7 - I couldn't click the inside of the box to toggle the box's mode when it is in the disabled mode! I could toggle the mode from enabled to disabled, but not the other way around.&lt;br /&gt;&lt;br /&gt;However, in pure Microsoft humor perhaps, I could toggle from disabled mode to enabled by clicking any of the colored borders...&lt;br /&gt;&lt;br /&gt;What did I do to fix the problem? Well, I found two ways&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Set the background color of the disabled mode to "white" or whatever that matches the background color of the whole page in general.&lt;/li&gt;&lt;li&gt;Or, use a 1x1 transparent spacer gif inside the link (a tag) and stretch it to fill the entire space.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;The latter approach suits me better - I don't need to define the background color.&lt;br /&gt;&lt;br /&gt;Yeah, it probably would work if the "colored-box" doesn't specify the background color and I introduce a class that does that (so I'll use both of them if I want the box in the enabled state). However, not only does that complicate my stylesheet, it also changes the meaning of "colored-box". So if I need to make sense of it, I need to rename the class and as a result of that, update my JS.&lt;br /&gt;&lt;br /&gt;So, no.&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-4762160102485685723?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/4762160102485685723/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=4762160102485685723' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4762160102485685723'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4762160102485685723'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2010/10/click-event-not-fired-if-background-is.html' title='Click event not fired if background is transparent (Internet Explorer 7)'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-2511699317507002408</id><published>2010-10-17T17:28:00.000+08:00</published><updated>2010-10-17T17:28:23.732+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='leadership'/><title type='text'>An ear is worth a thousand words</title><content type='html'>A familiar scenario perhaps - you're having lunch with the people who work for you. For some reasons, in one lunch, a conversation is deviating away from the casual topics. Suddenly, you sensed that the person you're talking to is trying to say something about work that relates to you... something that you don't want to hear, because if he is right, you have a problem.&lt;br /&gt;&lt;br /&gt;Usually, at that point, the person in the receiving end of the conversation would probably suit up - a helm, chainmail, a shield and probably even a mace, the knee-jerk reaction to battle the uncomfortable thought - to discredit it and to neutralize it. Whatever the means.&lt;br /&gt;&lt;br /&gt;This is the problem. Anything before isn't. In fact, anything that led up to the conversation is positive. It's great that team members escalate their concerns to their leader. It's very flattering, in one way - when team members tell their leader about the problems they are seeing, they may be asking for help from someone who they think could - the leader.&lt;br /&gt;&lt;br /&gt;Therefore, the last thing that leaders should do is to prove concerns that were raised as false or incorrect thoughts.&lt;br /&gt;&lt;br /&gt;Denied thoughts could turn into a perception. At the moment when a team member realizes or assumes that the leader isn't going to do anything about his concerns, he may change his perception of the leader. The leader, in the team member's perception, may be reduced to a very small person with a big voice but an empty head - someone incapable of looking after the team.&lt;br /&gt;&lt;br /&gt;.. and from there, it's just going to go worse. For instance, team members may lose interest &amp;nbsp;communicating their problems to their leader. Instead, they go sideways. The vent their frustration at their peers... and that's when it becomes cancer - a disease that may go unnoticed until at a very serious or threatening stage.&lt;br /&gt;&lt;br /&gt;I think, sometimes, the knee jerk reaction of leaders who try to fight potential problems by denial backfires.&lt;br /&gt;&lt;br /&gt;Instead, most of the time, it helps to listen. &amp;nbsp;Yes, just listen - until the concern makes sense, or how it makes sense in the team member's point of view. It's surprising what could come out at the end of it.&lt;br /&gt;&lt;br /&gt;For instance, I know of a team member who was talking about a problem in the team structure or skills-work distribution where people are not being assigned to do the things they are good at. That's a pretty big issue for the leader to fix if that is a problem.&lt;br /&gt;&lt;br /&gt;At this point, I think, some leaders may interrupt and tell the team member that the team is structured that way for the benefit of team flexibility and growing opportunities for everyone in it... and that may not be bullshit. It could be true. However, such interruptions have almost zero value. Why?&lt;br /&gt;&lt;br /&gt;The team member wasn't really talking about the structure. He just didn't like some of the things he had to do.&lt;br /&gt;&lt;br /&gt;Proving the team structure is right in that case, wouldn't have solved the problem. The actual problem could only be fixed if the leader knows what it is - and to find that out, listening may be required.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-2511699317507002408?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/2511699317507002408/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=2511699317507002408' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/2511699317507002408'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/2511699317507002408'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2010/10/ear-is-worth-thousand-words.html' title='An ear is worth a thousand words'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-6484104536918132557</id><published>2010-09-08T10:56:00.000+08:00</published><updated>2010-09-08T10:56:15.676+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='announcement'/><category scheme='http://www.blogger.com/atom/ns#' term='atlassian'/><title type='text'>AtlasCamp 2010 agenda live!</title><content type='html'>&lt;a href="http://www.atlassian.com/"&gt;Atlassian&lt;/a&gt;, the company that gave us &lt;a href="http://www.atlassian.com/software/jira/"&gt;JIRA&lt;/a&gt;, &lt;a href="http://www.atlassian.com/software/confluence/"&gt;Confluence&lt;/a&gt;, &lt;a href="http://www.atlassian.com/software/fisheye/"&gt;FishEye&lt;/a&gt;,&amp;nbsp;&lt;a href="http://www.atlassian.com/software/crucible/"&gt;Crucible&lt;/a&gt;, &lt;a href="http://www.atlassian.com/software/bamboo/"&gt;Bamboo&lt;/a&gt;&amp;nbsp;(and more) has just made their &lt;a href="http://www.atlassian.com/about/events/atlascamp/2010/agenda.jsp"&gt;AtlasCamp 2010 event agenda&lt;/a&gt; live. If you don't know about it already, AtlasCamp is their annual event for developers (plugins).&lt;br /&gt;&lt;br /&gt;Developers around the world would gather at a particular venue where they would discuss and share their experience and the products they are working on. It's also a great event &amp;nbsp;to learn more about Atlassian, the technology - generally a lot of geeky porn goodness!&lt;br /&gt;&lt;br /&gt;This year, their &lt;a href="http://www.atlassian.com/about/events/atlascamp/2010/"&gt;3rd AtlasCamp&lt;/a&gt;, they've introduced &lt;a href="http://confluence.atlassian.com/display/ATL/The+Great+AtlasCamp+Game+2010"&gt;a pre-event game for the participants&lt;/a&gt;! Looks like the fun has already started!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-6484104536918132557?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/6484104536918132557/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=6484104536918132557' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/6484104536918132557'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/6484104536918132557'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2010/09/atlascamp-2010-agenda-live.html' title='AtlasCamp 2010 agenda live!'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-4755200178066435929</id><published>2010-07-21T18:50:00.001+08:00</published><updated>2010-07-21T18:50:11.998+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><title type='text'>Showing a delayed tooltip with jQuery.hover()</title><content type='html'>I wanted a tooltip to only show if it has remained inside an element after a fixed amount of time. Sounds simple? Not to me - I nearly banged my head on the table for this one.&lt;br /&gt;&lt;br /&gt;At first, my approach was crude:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$(".mylement").hover(&lt;br /&gt;&amp;nbsp;&amp;nbsp;function(evt) {&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;// show tooltip at evt.pageX and evt.pageY, without any delay&lt;br /&gt;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;function(evt) {&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;// hide tooltip&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;);&lt;/pre&gt;&lt;br /&gt;It worked, but a bit funny. My absolutely positioned tooltip seems to randomly pick a coord near where my mouse cursor is. The effect is more pronounced if I swiftly move my mouse cursor into the element and then to the other end. In other words, quickly get from [0, 0] &lt;i&gt;of the element&lt;/i&gt; to [0, last pixel of element ].&lt;br /&gt;&lt;br /&gt;Grand testicles! I had no idea what went wrong. Ignoring that random positioning, I added a delay before showing my tooltip... and the problem became worse. The code, then, looked like the below.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$(".myelement").hover(&lt;br /&gt;&amp;nbsp;&amp;nbsp;function(evt) {&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;setTimeout(function() {&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;// show tooltip at evt.pageX and evt.pageY&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;}, 5000);&lt;br /&gt;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;function(evt) {&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;// hide tooltip&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;);&lt;/pre&gt;&lt;br /&gt;With the code above, if I were to move the mouse to enter the element, pause for about 2 seconds and in the 3 seconds before the popup shows, move my mouse cursor to the other end of the element, when the popup shows, it would be at the position where my mouse was 3 seconds ago.&lt;br /&gt;&lt;br /&gt;Fantastic? I think we know where the problem is.&lt;br /&gt;&lt;br /&gt;To fix that, my approach had to be changed to the following.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$(".myelement").mousemove(function(evt) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;$(this).data("coords", { pageX: evt.pageX, pageY: evt.pageY });&lt;br /&gt;}).hover(&lt;br /&gt;&amp;nbsp;&amp;nbsp;function(evt) {&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;setTimeout(function() {&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;var coords = $(this).data("coords");&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;// Show tooltip at coords.pageX, coords.pageY&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;}, 5000);&lt;br /&gt;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;function(evt) {&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;$(this).removeData("coords");&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;);&lt;/pre&gt;&lt;br /&gt;What a simple tooltip huh?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-4755200178066435929?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/4755200178066435929/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=4755200178066435929' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4755200178066435929'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4755200178066435929'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2010/07/showing-delayed-tooltip-with.html' title='Showing a delayed tooltip with jQuery.hover()'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-6852686631207599337</id><published>2010-05-31T11:29:00.001+08:00</published><updated>2010-05-31T11:33:43.487+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>Through the window</title><content type='html'>&lt;div&gt;I like to gaze at things on the other side of a window, from some height above ground. If I have the time, I could do it for hours. Really. Being able to take my time doing that, sometimes, people come into the same room and ask what I am looking at.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Despite the number of times I heard the question, it's something that has remained difficult to answer. If I have to blurt an honest response now, it would be... nothing.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It's perfectly fine if in the view is a lush, green, majestic mountain or streets bustling with traffic or a drizzle with people dashing for shelter in the background. It doesn't matter that much.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I just find it enjoyable to be alone with my thoughts, where in my view are things working without my presence. There's nothing depending on me. It's a place where I can retreat momentarily to acceptable solitude. Perhaps, the simplest way to describe what I am getting from looking at nothing is - peacefulness.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There is all there is - it just needs to be granted.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Of course, I do prefer certain views than others. For one, I love the view of the streets in the evening when it's drizzling, through a window slightly occupied by water droplets, accompanied by the clang and clatter of a cozy restaurant, almost drowned in the conversations the other patrons are enjoying.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Turning my head from the window, would be a smile of a companion sharing the same interest, a clink of glasses and a sip from some other part of the world.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-6852686631207599337?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/6852686631207599337/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=6852686631207599337' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/6852686631207599337'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/6852686631207599337'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2010/05/through-window.html' title='Through the window'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-7384489241113083469</id><published>2010-03-23T20:33:00.000+08:00</published><updated>2010-03-23T20:33:18.181+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='career'/><title type='text'>Dealing with blame mails</title><content type='html'>Blame mails - they come in at odd times and they always put the finger on you. We all get them. The difference is just with the frequency. From what I've observed, many of us respond to blame mails with very defensive replies. Worse still, some of us create more blame to divert the attention away.&lt;br /&gt;&lt;br /&gt;Assuming the problem is not your fault, and you have your innocence to protect (otherwise, it's a very different thing that we're talking about here). The knee-jerk response to this kind of situation is usually aggressive, often containing words or phrases that are regrettable a short while after the mail is sent. In the simplest analogy, getting a blame mail is like getting slapped for something you didn't do. The immediate response would probably be a punch, a kick or some sort of verbal assault.&lt;br /&gt;&lt;br /&gt;Introducing delay before the response, or a "cool-off" period, helps to reduce regrettable responses. After 30 minutes, you'll be calmer. You'll see things in the mail that you didn't see before. For instance, it may not even be a blame mail. It may be a cry for help, and if you could lend a hand, you'll earn a lot of respect.&lt;br /&gt;&lt;br /&gt;I once received a mail from a really upset sender, of a missing generate-report button in a system that I've helped developed. About half of the content is made up of hostile adjectives. Needless to say, the mail was copied to many recipients, some of them he reported to, directly and indirectly.&lt;br /&gt;&lt;br /&gt;Unsurprisingly, the button was there. It's just that there was a bit business logic that would hide the button. Rather than replying to all, with simplistic instructions that he could follow to find the button (in essence, make him look like a complete fool in front of his bosses), I waited and cooled off. Then I went to his desk to demonstrate how he could find the button… and then there was the magic. He was delighted, although slightly embarrassed.&lt;br /&gt;&lt;br /&gt;The guy was just desperate. He needed the report to be able to do something else. He needed an excuse if he couldn't. Whatever it was, he never wanted to create enemies.&lt;br /&gt;&lt;br /&gt;With the problem resolved, he was able to do what he needed to. He replied to his own mail mentioning us having worked together to resolve the problem and confirmed there was nothing wrong with the system.&lt;br /&gt;&lt;br /&gt;Since that day, I noticed more than a few times the gentleman was explaining the system to his peers. What was even better was that, if my ears had not failed me then, I overheard him defending the system. Of course, he had noticed me too, but even if we couldn't talk, we would exchange smiles.&lt;br /&gt;&lt;br /&gt;Now, if I had slapped him back, I think the story would be quite different.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-7384489241113083469?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/7384489241113083469/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=7384489241113083469' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7384489241113083469'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7384489241113083469'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2010/03/dealing-with-blame-mails.html' title='Dealing with blame mails'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-6676749974011066159</id><published>2010-03-21T21:15:00.001+08:00</published><updated>2010-03-21T21:29:38.246+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sale'/><category scheme='http://www.blogger.com/atom/ns#' term='used'/><category scheme='http://www.blogger.com/atom/ns#' term='parts'/><title type='text'>Used computer parts for sale</title><content type='html'>Spring cleaning fever hit me. I was going through my stuff and found more than a few computer parts that are in good condition, just sitting there, locked up in storage. So I thought I'd sell them &lt;b&gt;&lt;i&gt;cheap&lt;/i&gt;&lt;/b&gt;.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Interested? Read on!&lt;br /&gt;&lt;div&gt;&lt;div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;Macbook Pro Unibody accessories (all with boxes)- RM 80&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_3wFdvCO12KA/S6Ye3iK9WdI/AAAAAAAAACg/QUUJI4F8vEo/s1600-h/IMAGE_063.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="150" src="http://4.bp.blogspot.com/_3wFdvCO12KA/S6Ye3iK9WdI/AAAAAAAAACg/QUUJI4F8vEo/s200/IMAGE_063.jpg" width="200" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Screen protector&lt;/li&gt;&lt;li&gt;Keyboard protector&lt;/li&gt;&lt;li&gt;Palm rest protector&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;MSI Starforce Geforce2 GTS (with box) - RM20&lt;/li&gt;&lt;li&gt;Sapphire ATI Radeon X800 Pro (with box) - RM50&lt;/li&gt;&lt;li&gt;Kingmax 256MB DDR PC2400 - RM10&lt;/li&gt;&lt;li&gt;2x1GB Samsung DDR2-5300 SO-DIMM - RM85 for both, RM45 each. These actually came from my early 2008 Macbook Pro. I upgraded its RAM 1 month after I got it.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;As you'd expect, there's no guarantee. Why do I have so many computer parts? Upgrades.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you wish to know more, please write to me at &lt;i&gt;david dot cwy at gmail dot com&lt;/i&gt;. If you take a bundle of the items above, I'd be more than glad to give you a discount.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Hope to hear from you soon!&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-6676749974011066159?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/6676749974011066159/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=6676749974011066159' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/6676749974011066159'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/6676749974011066159'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2010/03/used-computer-parts-for-sale.html' title='Used computer parts for sale'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_3wFdvCO12KA/S6Ye3iK9WdI/AAAAAAAAACg/QUUJI4F8vEo/s72-c/IMAGE_063.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-8605387424063632552</id><published>2010-02-28T14:59:00.001+08:00</published><updated>2010-02-28T15:01:28.652+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='failure'/><title type='text'>How to order a delivery from McDonald's</title><content type='html'>For those that are not familiar with the process.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Make the call (1300-13-1300). Place the order.&lt;/li&gt;&lt;li&gt;Wait 40 minutes for a call from them telling you its on the way.&lt;/li&gt;&lt;li&gt;Wait 20 minutes before&amp;nbsp;calling them again asking about the order.&lt;/li&gt;&lt;li&gt;The delivery arrives for you to find a missing item and Coke spilled all over.&lt;/li&gt;&lt;li&gt;Call them again to tell them about the missing item.&lt;/li&gt;&lt;li&gt;Call them again, 20 minutes later to tell them the item is still missing.&lt;/li&gt;&lt;li&gt;Continue waiting.&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/13841310-8605387424063632552?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/8605387424063632552/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=8605387424063632552' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8605387424063632552'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8605387424063632552'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2010/02/how-to-order-delivery-from-mcdonalds.html' title='How to order a delivery from McDonald&apos;s'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-8047078172898883757</id><published>2010-02-14T12:53:00.000+08:00</published><updated>2010-02-14T12:53:54.849+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>Traveling around the world</title><content type='html'>It's a pretty festive period this month. There are Chinese New Year, Valentine's and Chap Goh Mei. It's also the shortest month in the year.&lt;br /&gt;&lt;br /&gt;Friends and family would take advantage of those special occasions to meet up and celebrate. Venues would be chosen, drinks will be poured, the kitchen would be bustling - all for the fragments of memories that we would bring back as souvenirs. I love it! It's fantastic!&lt;br /&gt;&lt;br /&gt;Every celebration is an experience that I could relate to a few countries. Sound strange? I thought so. For a while, I couldn't really put to words how it felt like... Maybe it's the lack of effort, or maybe I haven't really thought about it. However, on this hot, lazy day of the first day of Chinese New Year in 2010, it feels appropriate to try.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Accomplishment&lt;/b&gt;&lt;br /&gt;I'd call moments like this self-recognition of success. It's when I know things will work. It's when I know the pieces will fall into places. Reaching home is usually New Zealand - cool, calm and thin. There will be the majestic beauty of nature, one which the appreciation is widely common but deeply individual. The feeling is almost victorious, but yet, in its own way, subtle. Reminiscence is the perfect companionship.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Success&lt;/b&gt;&lt;br /&gt;It's when hard labor is validated. For instance, when sales figures go up, customer satisfaction rises, new deals signed. There's no doubt. Things &lt;i&gt;are&lt;/i&gt; better. Cheers, if not already present, is imminent in the seconds to come. Faces light up, knowing what to expect in the turn of the following months. It's all good. Well done. Success is mine and everyone else's.&lt;br /&gt;&lt;br /&gt;Moments like that usually remind me of small bars with images of Marilyn Munroe seated in the center of some men smoking cigars, having a great time. Everyone is making statements to each other, highly noticeable, almost pretentious, but at the same time, really enjoyable. Chemical, but strangely necessary.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Gatherings&lt;/b&gt;&lt;br /&gt;They always start awkward to me. Distant, acidic and when multiplied by the number of people showing up, intoxicating. People dress up. The make up is heavy. The suits are well ironed. A casual outing dressed formally, decorated with smiles and grins. There are people being bold in every recognizable sense - particularly in smell and sight. Very very heavy.&lt;br /&gt;&lt;br /&gt;Fortunately, with a bit of air, it gets better. The experience usually betters when the initial conversations are left a bit cold, when people start to tune and balance with the group's interest of the night. Eventually, the cosmetics will blend, the suits make sense. If it isn't heavy, it's probably too light.&lt;br /&gt;&lt;br /&gt;In some ways, I could attribute the experience to Australia. Strong at first, but followed by understanding and balance. It probably wouldn't work any other way.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-8047078172898883757?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/8047078172898883757/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=8047078172898883757' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8047078172898883757'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8047078172898883757'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2010/02/traveling-around-world.html' title='Traveling around the world'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-3717531728666015316</id><published>2010-02-11T18:20:00.001+08:00</published><updated>2010-02-11T18:23:45.826+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><category scheme='http://www.blogger.com/atom/ns#' term='leadership'/><category scheme='http://www.blogger.com/atom/ns#' term='career'/><title type='text'>5 easy steps to quickly make a decision</title><content type='html'>Most of our lives are hectic, be it personal or work. We need to make decisions all the time and we want to make the &lt;i&gt;best&lt;/i&gt; ones... and we want to make them the quickest we can.&lt;br /&gt;&lt;br /&gt;I've seen some pretty comprehensive decision making processes. While I think they do help decision makers to work towards the best decisions possible, they are usually too complex, too heavy that they get forgotten with time or just practiced incorrectly.&lt;br /&gt;&lt;br /&gt;I'm a fan of making quick decisions. It helps to push things forward. It minimizes bottlenecks. I go home happy. Here's how I do it.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1. Try to understand the problem&lt;/b&gt;&lt;br /&gt;Notice the word "try". The goal here isn't to fire every neuron I have to understand the problem (although that would be ideal). The goal is to decide whether I am the best person to make the decision, or if there is someone else who is. If there is someone more suitable to make the call, I'll just pass it over. This is not incompetence, as some would probably take it. This is efficiency.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span"&gt;&lt;b&gt;2. Define what the critical outcome is&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;Imagine you're driving to a meeting scheduled to start at 9:30 a.m. At 9:15, you reached a crossroad that gives you three options that could lead you to the venue. You know each option has its disadvantages. Let's say:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;The left turn would take 30 minutes. It's your usual way.&lt;/li&gt;&lt;li&gt;Straight is a road you have never taken before, but you've heard that 15 minutes is quite possible.&lt;/li&gt;&lt;li&gt;The right turn is a really difficult drive, but with care, it will just take 10 minutes.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;While it is tempting, this isn't when I'd decide. This is when I'd define my critical goal, the goal that must be achieved with the decision I make. In this case, it would to be able to attend the meeting. I'd categorize other factors (like punctuality) as important, but not critical.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span"&gt;&lt;b&gt;3. Discard options that can fail your critical goal&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;With a critical goal defined, it's pretty easy to rule out the bad options.&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Option 3 is not acceptable because there's a chance I'd be unable to attend the meeting at all. Imagine if I get into an accident.&lt;/li&gt;&lt;li&gt;Option 2 is not acceptable because it can completely fail my critical goal. Imagine if I get lost.&lt;/li&gt;&lt;li&gt;Option 1 is acceptable because in the worst case scenario, I'd just be 15 minutes late. I'd definitely be able to attend the meeting.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Some may say it's faster to choose the option that fits the &lt;i&gt;ideal&lt;/i&gt; outcome the best. I have to say I don't entirely agree with that. Choosing the best available option does not tell me if it is worth taking at all. For instance, the best option may be an option that would fail the critical outcome. If I happen to decide on something that would fail the critical outcome, I really have chosen to fail.&lt;br /&gt;&lt;br /&gt;But what happens where there is not one option that can be taken? Well, it's not all bad. If we can assert that we're bound to fail, it shifts the mindset from 'me' to 'us'. Instead of me attending the meeting, I'd think of someone who can do that on my behalf. When your mindset shifts, you can see more options. But to do that, you really need to know if you're screwed.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span"&gt;&lt;b&gt;4. Optimize your options&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;Ok. So option 1 is acceptable… but can it be better? Actually, it can be. It'd be great to be on time.&lt;br /&gt;&lt;br /&gt;So if I take the left turn, I'll be 15 minutes late. To be on time, I need to be there at 9:30, or the meeting has to start 15 minutes later. It's obvious that being there at 9:30 would be the best. The question is, how can that be done? Is there a way?&lt;br /&gt;&lt;br /&gt;Yes, there is. Teleconference. You could dial into the meeting for the first 15 minutes.&lt;br /&gt;&lt;br /&gt;If teleconferencing is not available, I'd probably call the organizers and ask if they could postpone the meeting by 15 minutes.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;5. Stick with your decision&lt;/b&gt;&lt;br /&gt;Don't be an ass. When you've decided on something, you've set things in motion. Sine then, people would've made decisions based on that. When you recall your decision, you're asking people to revert the things they've started, and that cascades to every level involved. It's a lot of unnecessary work, especially if the gains are minor.&lt;br /&gt;&lt;br /&gt;But what if the decision is wrong? Well, I don't believe in wrong decisions. At worst, there are only misinformed decisions - decisions that were made based on inadequate input or experience. If you've followed similar steps to reach to a decision, you couldn't make a better one if you could rewind time.&lt;br /&gt;&lt;br /&gt;Recalling a decision doesn't make things right. If you think you've made a mistake, you try to think of recovery steps or damage control. There is nothing that I know of that can prevent us from making mistakes in our decisions. It happens all the time. What matters most then is that we take lesson of it and use it to make a better decision next time.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-3717531728666015316?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/3717531728666015316/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=3717531728666015316' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/3717531728666015316'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/3717531728666015316'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2010/02/5-easy-steps-to-quickly-make-decision.html' title='5 easy steps to quickly make a decision'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-4835036749011818656</id><published>2010-01-17T22:27:00.001+08:00</published><updated>2010-01-17T22:36:47.323+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tourism'/><category scheme='http://www.blogger.com/atom/ns#' term='vacation'/><category scheme='http://www.blogger.com/atom/ns#' term='failure'/><title type='text'>Why you should stay away from Amantra Resort &amp; Spa, Ko Lanta</title><content type='html'>After much browsing online, me and my girfriend finally decided to book &lt;a href="http://www.amantraresort.com/"&gt;Amantra&amp;nbsp;Resort &amp;amp; Spa&lt;/a&gt; for a 5 days 4 nights stay over the new year's eve. It looked nice and we really liked what we saw. So we confirmed it via &lt;a href="http://www.agoda.com/"&gt;Agoda&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Everything was great at first. We even asked the resort to arrange the transfer service (from Krabi airport to the resort). Email responses were prompt and we got what we wanted - we would have a taxi waiting for us at the airport, arranged by the resort.&lt;br /&gt;&lt;br /&gt;When we arrived at the airport that day, at about 2pm, there was no sign of the driver that the resort promised us. To be sure, we waited for about 30 minutes. Still no sign.&lt;br /&gt;&lt;br /&gt;As it was getting late, we had to hire a taxi ourselves, from the airport.&lt;br /&gt;&lt;br /&gt;The taxi brought us to the resort. The journey was almost 2 hours. So we reached the resort's reception at about 4pm. When we presented our Agoda coupon, the receptionist told us that the resort was overbooked. We had to be transferred to another resort. That was really disappointing, and I must say, I was quite upset. The trip was planned way in advanced, and we booked the resort more than a month earlier! It was quite unacceptable, really.&lt;br /&gt;&lt;br /&gt;I thought it was pure bad luck‚ until another couple tried to check in after us. Apparently, they had the same problem. Also, they faced the same problem with the transfer service (or lack of) the resort provided.&lt;br /&gt;&lt;br /&gt;Eventually, all of us (including the couple) agreed to be transferred. There was no other choice, really. The resort that we got transferred to was &lt;a href="http://www.dreamteamresort.com/"&gt;Dream Team&lt;/a&gt;. It looked good and the service was excellent. However, we travelled to Ko Lanta with certain expectations‚ and I certainly expected a beach that I could walk to from the resort and swim. That expectation could not be met by Dream Team, unfortunately. Its beach was very rocky.&lt;br /&gt;&lt;br /&gt;In all, we spent the 4 nights at Dream Team. While my vacation wasn't entirely destroyed, and the assistant manager of Amantra Resort &amp;amp; Spa did offer tokens of apology to us, I have to say, it wasn't pleasant at all to get so upset on the first day of the vacation. I wanted to go to Ko Lanta to have a great time. It started miserably‚ and if I say that didn't affect my vacation at all, I'd be lying.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-4835036749011818656?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/4835036749011818656/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=4835036749011818656' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4835036749011818656'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4835036749011818656'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2010/01/why-you-should-stay-away-from-amantra.html' title='Why you should stay away from Amantra Resort &amp; Spa, Ko Lanta'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-6574023707718015040</id><published>2009-12-15T23:43:00.000+08:00</published><updated>2009-12-15T23:43:35.960+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='automated testing'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='codegeist'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='announcement'/><category scheme='http://www.blogger.com/atom/ns#' term='confluence'/><category scheme='http://www.blogger.com/atom/ns#' term='atlassian'/><title type='text'>Testing Confluence plugins with Selenium</title><content type='html'>JavaScript testing has always been a blind spot in the &lt;a href="https://developer.atlassian.com/jira/browse/CONFPLUGTEST"&gt;Confluence Plugin Test Library&lt;/a&gt;. All the tests that were written with the test library until now has been based on &lt;a href="http://jwebunit.sourceforge.net/"&gt;JWebUnit&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;As JavaScript becomes more popular in applications, the need to get them tested automatically also becomes more important. So that's why I've integrated &lt;a href="http://seleniumhq.org/"&gt;Selenium&lt;/a&gt; support in the latest beta (v. 2.2-beta4) of the test library.&lt;br /&gt;&lt;br /&gt;At the moment, the implementation is minimalistic. In 2.2-beta4, you get a "tester" that extends &lt;a href="http://release.seleniumhq.org/selenium-remote-control/0.9.0/doc/java/com/thoughtworks/selenium/DefaultSelenium.html"&gt;DefaultSelenium&lt;/a&gt; to provide functionality consistent with the &lt;a href="http://svn.atlassian.com/fisheye/browse/public/contrib/confluence/confluence-plugin-func-test/trunk/src/main/java/com/atlassian/confluence/plugin/functest/JWebUnitConfluenceWebTester.java?r=37175"&gt;JWebUnit tester&lt;/a&gt; in the current versions of the test library. Yes, that means you can upload any files to any URL in Confluence. You're not confined, in that aspect, to the capabilities of Selenium RC.&lt;br /&gt;&lt;br /&gt;To show how Selenium testing can be done, I've added Selenium tests for the &lt;a href="http://confluence.atlassian.com/display/CONFEXT/Confluence+Page+History+Slider+Plugin"&gt;Confluence Page History Plugin&lt;/a&gt; (&lt;a href="http://blogs.atlassian.com/developer/2009/10/and_the_winners_of_codegeist_iv_are.html"&gt;Codegeist 2009 Most elegant use of JavaScript category winner&lt;/a&gt;). &lt;a href="http://svn.atlassian.com/fisheye/changelog/public/?cs=38019"&gt;This changeset&lt;/a&gt; shows:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The base test class you need to extend.&lt;/li&gt;&lt;li&gt;The project dependencies and other POM setups related to Selenium testing.&lt;/li&gt;&lt;li&gt;A build script suitable for use in CI servers (usually headless)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;I hope you'll find the new Selenium support useful. If you come across issues, please file it &lt;a href="https://developer.atlassian.com/jira/browse/CONFPLUGTEST"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Cheers!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-6574023707718015040?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/6574023707718015040/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=6574023707718015040' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/6574023707718015040'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/6574023707718015040'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/12/testing-confluence-plugins-with.html' title='Testing Confluence plugins with Selenium'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-663752886014176335</id><published>2009-11-22T13:31:00.000+08:00</published><updated>2009-11-22T13:31:40.634+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><category scheme='http://www.blogger.com/atom/ns#' term='humor'/><title type='text'>Want a pony - Google death notification</title><content type='html'>&lt;div&gt;&lt;span style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="font-size: 11px;"&gt;&lt;div&gt;&lt;div&gt;Google, when I die, could you please send this email/wave to people in my contacts?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;From: David Chui&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;Sent: &amp;lt;Date of death&amp;gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;To: &amp;lt;Name of recipient&amp;gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;CC: &amp;lt;Family members&amp;gt;, &amp;lt;Neighbors&amp;gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;Subject: RIP David Chui 1983 - &amp;lt;Year of death&amp;gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;Hi &amp;lt;Name of recipient&amp;gt;,&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;Apparently, I have died (see date in the subject). Obviously, I didn't have the time to write you a personalized message of my death, but depending on your relationship with me, could you please click on one of the actions below?&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;i&gt;[Mark sender as spammer] - You have always hated me and you don't want to hear from me anymore. However, I doubt I'll be sending you more emails after this.&lt;/i&gt;&lt;/li&gt;&lt;li&gt;&lt;i&gt;[Update my birthday calendar] - When you click this, my birthday would not recur after the year of death. Optionally, you could create a new recurring event of my death for your annual celebration pleasure.&lt;/i&gt;&lt;/li&gt;&lt;li&gt;&lt;i&gt;[Tweet about David's death] - When you click this, you'll post a new update on your Twitter that says "w00t! &lt;a href="http://twitter.com/davidchui"&gt;@davidchui&lt;/a&gt; is finally dead".&lt;/i&gt;&lt;/li&gt;&lt;li&gt;&lt;i&gt;[Post a message on David's Facebook wall] - When you click this, you'll post a message on &lt;a href="http://www.facebook.com/dchui"&gt;my Facebook&lt;/a&gt; wall that says "Let me know if it's hot down there!"&lt;/i&gt;&lt;/li&gt;&lt;li&gt;&lt;i&gt;[WTF?] - If you don't know what to do, I'll take care of the Rickrolling for you.&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;Thanks and have a nice life! By the way, how's the dog?&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-663752886014176335?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/663752886014176335/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=663752886014176335' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/663752886014176335'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/663752886014176335'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/11/want-pony-google-death-notification.html' title='Want a pony - Google death notification'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-1777848679889524011</id><published>2009-11-19T20:14:00.001+08:00</published><updated>2009-11-19T20:17:31.592+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><category scheme='http://www.blogger.com/atom/ns#' term='humor'/><title type='text'>Deciphering the work-speak</title><content type='html'>It's close to the end of the year. To be specific, it's November now. The year will officially end with the month. Between then and January is a period of time used mostly for reminding ourselves why we could last the 11 months.. and also one of the best times to reflect and think, maybe for a better year ahead.&lt;br /&gt;&lt;br /&gt;The year has been full of lingo to me. No, not the terms that people tend to use to sound really smart, but the type of lingo used throughout organizations to get things done. It's work-speak and it's funny when you think about what it could mean. Here's a collection of some of the more memorable ones that I've heard.&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;"Hi"&lt;/b&gt; - I've got a problem for you, kiddo.&lt;/li&gt;&lt;li&gt;&lt;b&gt;"I'd like to have a catch up with you soon"&lt;/b&gt; - I smell you and I will find out where it smells and I'm going to make sure that stench stays until I get hounds on your ass.&lt;/li&gt;&lt;li&gt;&lt;b&gt;"So… where are we now?"&lt;/b&gt; - Why the fuck are things taking so fucking long?&lt;/li&gt;&lt;li&gt;&lt;b&gt;"We need a plan"&lt;/b&gt; - I have no idea how to solve this shit. Now it's your problem too.&lt;/li&gt;&lt;li&gt;&lt;b&gt;"We need leave planning"&lt;/b&gt; - You're not allowed to be away for too long.&lt;/li&gt;&lt;li&gt;&lt;b&gt;"We need day planning"&lt;/b&gt; - Do let me know how you plan to fuck yourself &lt;i&gt;differently&lt;/i&gt; everyday.&lt;/li&gt;&lt;li&gt;&lt;b&gt;"I can give you code snippets"&lt;/b&gt; - I've got a bunch of useless code pretending to solve a problem in the worst way possible.&lt;/li&gt;&lt;li&gt;&lt;b&gt;"The build is green"&lt;/b&gt; - The Maven problems are fixed.&lt;/li&gt;&lt;li&gt;&lt;b&gt;"This sucks"&lt;/b&gt; - Ah, fuck it.&lt;/li&gt;&lt;li&gt;&lt;b&gt;"I'm going lunch"&lt;/b&gt; - The finger.&lt;/li&gt;&lt;li&gt;&lt;b&gt;"X is moving on"&lt;/b&gt; - Another one bites the dust.&lt;/li&gt;&lt;li&gt;&lt;b&gt;"Would you like to step up"&lt;/b&gt; - Would you like a turn in the electric chair?&lt;/li&gt;&lt;li&gt;&lt;b&gt;"Great job!"&lt;/b&gt; - Finally, you've done something right.&lt;/li&gt;&lt;li&gt;&lt;b&gt;"You're the employee of the month"&lt;/b&gt; - Congratulations on getting yourself hated by everyone.&lt;/li&gt;&lt;li&gt;&lt;b&gt;"Version 1.0 released"&lt;/b&gt; - We've finally decided to ship a SNAPSHOT.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Got more?&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-1777848679889524011?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/1777848679889524011/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=1777848679889524011' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/1777848679889524011'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/1777848679889524011'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/11/deciphering-work-speak.html' title='Deciphering the work-speak'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-1312300592522645078</id><published>2009-10-25T13:49:00.001+08:00</published><updated>2009-10-25T13:57:09.714+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='codegeist'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='award'/><category scheme='http://www.blogger.com/atom/ns#' term='announcement'/><category scheme='http://www.blogger.com/atom/ns#' term='results'/><category scheme='http://www.blogger.com/atom/ns#' term='atlassian'/><title type='text'>I won the 'Most elegant use of Javascript' category in Atlassian Codegeist 2009</title><content type='html'>&lt;a href="http://www.atlassian.com/codegeist/"&gt;Codegeist&lt;/a&gt; is an annual plugin development competition held by &lt;a href="http://www.atlassian.com/"&gt;Atlassian&lt;/a&gt; to inspire developers worldwide to write fantastic plugins for their products. 2009 is the fourth time around and I've submitted two entries for it. They are:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://confluence.atlassian.com/display/CONFEXT/Confluence+Page+History+Slider+Plugin"&gt;Confluence Page History Slider Plugin&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://confluence.atlassian.com/display/CONFEXT/Confluence+FlotChart+Plugin"&gt;Confluence FlotChart Plugin&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;The plugins were submitted a few weeks ago. Since then, I waited quite anxiously for the results. Today, the wait is over. Atlassian has announced the results publicly in &lt;a href="http://blogs.atlassian.com/developer/2009/10/and_the_winners_of_codegeist_iv_are.html"&gt;this blog post&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;… and I won the &lt;i&gt;'Most elegant use of Javascript'&lt;/i&gt; category! What sweet news to receive!&lt;br /&gt;&lt;br /&gt;In this post, I'd also like to congratulate the other winners! You guys rock! Keep the awesome plugins coming!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-1312300592522645078?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/1312300592522645078/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=1312300592522645078' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/1312300592522645078'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/1312300592522645078'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/10/i-won-most-elegant-use-of-javascript.html' title='I won the &apos;Most elegant use of Javascript&apos; category in Atlassian Codegeist 2009'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-7870983846656681366</id><published>2009-09-22T17:00:00.000+08:00</published><updated>2009-09-22T17:00:05.414+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='user interfaces'/><category scheme='http://www.blogger.com/atom/ns#' term='development career'/><title type='text'>Simple sign-up forms win</title><content type='html'>So you've built an amazing web application. You've gone through months of research and development to come up with a formula that you think will win. You got the feedback that gives you the warm feeling that the application is going to be a hit. Everything is ready. You just need to wait for the masses of users.&lt;br /&gt;&lt;br /&gt;But then, there's the sign-up.&lt;br /&gt;&lt;br /&gt;Sign-up is a problem because it is a hurdle that users need to jump over before they can start benefitting from our applications. Although it is a one-time process, the hassle can be too much. Sometimes, the hassle makes people lose interest… and that's really the worst thing that can happen - lose, lose.&lt;br /&gt;&lt;br /&gt;If a big user base is vital for the application's success, sign-up must be as little hassle as possible. One of the biggest hassle with sign-up is the complexity of the sign-up forms. Have you seen sign-up forms that span multiple pages? Or how about sign-up forms that ask too much about you that it becomes uncomfortable?&lt;br /&gt;&lt;br /&gt;I think most of us have.&lt;br /&gt;&lt;br /&gt;The ideal sign-up form should be smart and asks as little as possible. The objective is to get the user in to experience the &lt;i&gt;key&lt;/i&gt; features of the application as quickly and as easy as possible. So, what are the fields that should be there in the sign-up form? I think most sign-up forms can be simplified to only contain these fields:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Email address.&lt;/li&gt;&lt;li&gt;An optional, non-repeating password field.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;How does it work? For the non-imaginative, after the form is submitted, an email will be sent to the user at the email address specified, letting him or her know of the first-time-only-instant-login link. When that link is clicked, the authenticity of the email account is verified and the user is logged in immediately.&lt;br /&gt;&lt;br /&gt;The content of the email can be customized to contain helpful hints for the new user. But that's not what this post is about.&lt;br /&gt;&lt;br /&gt;If the user did not specify a password in the optional password field, a server-generated password should be included in the email. In this scenario, the application should prompt the user to change the password after he or she logs in the first time.&lt;br /&gt;&lt;br /&gt;Sign up forms like this save users some hassle. They include:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Choosing a handle/nick that is unique in the system (usually required if such field is present)&lt;/li&gt;&lt;li&gt;Type Captcha words.&lt;/li&gt;&lt;li&gt;Repeat the password for confirmation.&lt;/li&gt;&lt;li&gt;Filling up too much details.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Some may ask, will error handling and application features accessibility be harmed? Well, not really.&lt;br /&gt;&lt;br /&gt;The most obvious thing that can go wrong during sign up is when the user forgets the password. But if the application records the user's email address, all it needs to do is to allow the user to ask for the link to reset his/her password. That link will only be sent via email (to the user's inbox).&lt;br /&gt;&lt;br /&gt;If there are other parts of the application that need data that is not captured during sign-up, don't worry. The related parts just need to ask for the data &lt;i&gt;when&lt;/i&gt; the user wants to use them. This provides context to the user why he or she is providing the details. If the user finds that it is ok to provide those details, he or she will do it. The rule is simple, let them know why the system needs to know so much about them and only ask for the data when there is a need to.&lt;br /&gt;&lt;br /&gt;For instance, let's say you're building a social networking site and the application has the ability to find your old school mates. Instead of providing a few text fields during sign-up that ask for education history, it would be better to defer that until the user wants to find his/her old school mates.&lt;br /&gt;&lt;br /&gt;Sign-ups should be easy. Features should be discoverable.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-7870983846656681366?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/7870983846656681366/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=7870983846656681366' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7870983846656681366'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7870983846656681366'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/09/simple-sign-up-forms-win.html' title='Simple sign-up forms win'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-4972378551968068795</id><published>2009-07-19T23:54:00.004+08:00</published><updated>2009-07-20T00:29:35.287+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><category scheme='http://www.blogger.com/atom/ns#' term='organization'/><title type='text'>Get rid of the clutter</title><content type='html'>&lt;div&gt;Let's face it. Most of our desks look like they were hit by a tornado. I believe most of us know the disadvantages of messy desks, the reasons (excuses) for them and why it is good to clear the clutter. But how about the "how"?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;My workstation consists of only my desk, my chair, my notebook, input devices and my coffee mug/cup. That's all. Despite its simplicity, it does not inhibit good productivity. In fact, it helps me to focus.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, I thought I'd share some of the things I've thought of and done to clear the clutter.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;1. Replace physical tools with virtual ones&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;i&gt;In tray&lt;/i&gt; — With my (gulp) email client and task lists. I use an issue tracker as a helper to this.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Calendar&lt;/i&gt; — With Google Calendar. It's sharing features, (SMS) reminders, and interoperability (iCalendar) rock.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Notes&lt;/i&gt; — With Google Notebook. Yes, it's a service that will no longer get maintenance time, but it's one of the best tools I know that allows me to quickly make notes and have them accessible anywhere. It has the advantage of being searchable too.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Files/folders&lt;/i&gt; — My computer's file system or my company's file storage servers with references to them from my company's wiki. I generally prefer storing information that has a long validity period in file servers, as they get backed up automatically. However, I do keep recent documents in my computer, where they are searchable anywhere (thanks, Spotlight).&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Replacing physical tools with virtual ones did require changes in my working style. There's a bit of a learning curve as well. However, after I got used to the virtual tools, reaching to them is really just as easy as with physical ones. It's almost muscle memory. You'd be surprised by the amount of space you'll free up by moving to virtual tools.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;&lt;b&gt;2. Put away the things you don't need (frequently)&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;i&gt;Photo frames&lt;/i&gt; — I'm at my work desk to work. Having pictures of my personal life at work is just as awkward as having a picture of my work at home.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Complete stationar&lt;/i&gt;y — I don't use stuff like scissors, knifes, letter openers, erasers, pencils and tapes all that often. So they had to go.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Own name cards&lt;/i&gt; — I'd expect people who is in the range of giving cards to would either a) have one already or b) know me. &lt;/li&gt;&lt;li&gt;&lt;i&gt;Other name cards&lt;/i&gt; — Contact details of people I know should either be in a) my brain  or b) my virtual address book.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Receipts&lt;/i&gt; — Bin them or file them (if they are reimbursable).&lt;/li&gt;&lt;li&gt;&lt;i&gt;Money&lt;/i&gt; — It should be in my wallet or my bank account.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Food/snacks&lt;/i&gt; — If it can't be finished in the day it was taken, it should go back to the source.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Magazines&lt;/i&gt; — If I can make use of them there, something is odd. Magazines or other non-work related reading materials should be in the leisure rack.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;3. Optimization&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;i&gt;Reduce the number of cables&lt;/i&gt; — Cables make work desks look like snake pits. Invest in wireless networking and input devices (e.g. the mouse and keyboard) to help clear the clutter.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Stop using a mouse pad&lt;/i&gt; — If you're using a laser based mouse and a non—glossy work desk, you don't need a mouse pad.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Reduce the number of devices &lt;/i&gt;— If you have two monitors, it's good to think if it would be better to have a big one rather than two smaller ones. Two monitors mean two sets of cables for them. One big one will give you the same virtual space, but with significantly fewer cables.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Keep keys in lockers or drawers&lt;/i&gt;. If neither is available, ask for one.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And once you've gotten rid of the clutter, remember to &lt;i&gt;keep&lt;/i&gt; it that way.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-4972378551968068795?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/4972378551968068795/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=4972378551968068795' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4972378551968068795'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4972378551968068795'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/07/get-rid-of-clutter.html' title='Get rid of the clutter'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-5878904724765400624</id><published>2009-06-27T13:09:00.004+08:00</published><updated>2009-06-27T13:26:24.098+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='user interfaces'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='jquery'/><title type='text'>Upload files without refreshing the page with just JavaScript</title><content type='html'>So you want a simple way to &lt;i&gt;dynamically&lt;/i&gt; upload a file? Using JavaScript and nothing more? I may have the solution for you... well no, Google has. But I'll just add some details here.&lt;br /&gt;&lt;br /&gt;But first, we need to know our limitations:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;XHR cannot be used to upload files.&lt;/li&gt;&lt;li&gt;Even if it can be (which I'm 99% sure it can't), our code will look like shit.&lt;/li&gt;&lt;/ul&gt;Next, we need to know what we really mean by "simple".&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Don't want to use Java Applets.&lt;/li&gt;&lt;li&gt;Don't want to use Flash.&lt;/li&gt;&lt;li&gt;Simple so that it works with FireFox, Safari and IE.&lt;/li&gt;&lt;li&gt;Although desirable, simple means there will be no progress bar. However, the file will get uploaded without the page refreshed.&lt;/li&gt;&lt;/ul&gt;The best way I know that can do the above is with the iframe hack. There are probably several trillion examples out there. However, those that appeared in the first page of my Google search results are either incomplete, hard to follow or they didn't point out the caveats in an obvious manner.&lt;br /&gt;&lt;br /&gt;So, here's my attempt using jQuery.&lt;br /&gt;&lt;br /&gt;Typically, file uploads are done with a form and a file input. When the form is submitted, the file gets uploaded and the page refreshes with the results. The iframe hack uses that. Except, there is another component. The iframe, of course.&lt;br /&gt;&lt;br /&gt;With the iframe hack, you will have your normal form, but the HTML returned by its submission will be 'sent' to the iframe. The iframe is invisible. So users will not see the page refreshed.&lt;br /&gt;&lt;br /&gt;So, let's start with an example:&lt;br /&gt;&lt;h2&gt;The form&lt;/h2&gt;&lt;pre&gt;&lt;tt&gt;&amp;lt;form id="topsecretuploadform" method="POST" action="/path/to/file/upload/handler" enctype="multipart/form-data"&amp;gt;&lt;br /&gt; &amp;lt;input type="file" name="topsecret"&amp;gt;&lt;br /&gt; &amp;lt;input type="submit" value="Let the world see it"&amp;gt;&lt;br /&gt; &amp;lt;iframe id="topsecretiframe" name="youcannnotseeme" src="" style="display: none;"&amp;gt;&amp;lt;/iframe&amp;gt;&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;/tt&gt;&lt;/pre&gt;&lt;h2&gt;You will need some JavaScript to set the target of the form.&lt;/h2&gt;You need to set the target to the name of the iframe. If you do that, the HTML generated by the server after it has processed the upload will be sent to the invisible iframe.&lt;br /&gt;&lt;pre&gt;&lt;tt&gt;&lt;br /&gt;jQuery(function($) {&lt;br /&gt; var iframe = $("#topsecretiframe");&lt;br /&gt;&lt;br /&gt; $("#topsecretuploadform").submit(function() {&lt;br /&gt;   this.target = iframe.attr("name");&lt;br /&gt;   iframe.get(0).processContent = true; // The use of this flag will be explained&lt;br /&gt; });&lt;br /&gt;})&lt;/tt&gt;&lt;/pre&gt;&lt;h2&gt;You will need some JavaScript to tell the user that the upload is complete&lt;/h2&gt;&lt;pre&gt;&lt;tt&gt;jQuery(function($) {&lt;br /&gt; $("#topsecretiframe").load(function() {&lt;br /&gt;   if (!this.processContent)&lt;br /&gt;     return;&lt;br /&gt;&lt;br /&gt;   var iframeDocument = this.contentWindow || this.contentDocument;&lt;br /&gt;   iframeDocument = iframeDocument.document ? iframeDocument.document : iframeDocument;&lt;br /&gt;   var iframeBodyElement = iframeDocument.body;&lt;br /&gt;   // The 3 lines above is a really easy and cross browser way of getting the body of the document object of the iframe&lt;br /&gt;&lt;br /&gt;   // Assuming that we know certain elements will be present in the returned HTML after a successful upload,&lt;br /&gt;   // the code below shows how you can know from the script.&lt;br /&gt;&lt;br /&gt;   var someHtml = $(iframeBodyElement);&lt;br /&gt;   if (iframeBodyElement.find("div.successfulupload").length &amp;gt; 0)&lt;br /&gt;     alert('Upload done successfully.');&lt;br /&gt;   else&lt;br /&gt;     alert('Oh shit. Upload went fubar. Look at your logs.');&lt;br /&gt; });&lt;br /&gt;});&lt;/tt&gt;&lt;/pre&gt;&lt;h2&gt;Caveats&lt;/h2&gt;&lt;h3&gt;The reason for the processContent flag.&lt;/h3&gt;Some browsers will call the iframe onload function the first time it loads (with empty content). Some don't. If that flag didn't exists, using our example, some users will be alerted of something that haven't even started doing.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Iframe names must be set statically.&lt;/h3&gt;Don't do this:&lt;pre&gt;&lt;tt&gt;jQuery("iframe.thosehackyiframes").each(function(index) {&lt;br /&gt; this.name = "wahooga_" + index;&lt;br /&gt;});&lt;/tt&gt;&lt;/pre&gt;From my testing, that does not work. Set them in a static way.&lt;br /&gt;&lt;h2&gt;So&lt;/h2&gt;So, you get dynamically uploaded files without XHR. In between the form submission and the iframe load function call, you can insert all the bells and whistles. You may actually want some sort of animated waiting gif. But that's something I'm not going to talk about.&lt;br /&gt;&lt;br /&gt;Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-5878904724765400624?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/5878904724765400624/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=5878904724765400624' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/5878904724765400624'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/5878904724765400624'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/06/upload-files-without-refreshing-page.html' title='Upload files without refreshing the page with just JavaScript'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-7361167252655709921</id><published>2009-06-23T18:48:00.004+08:00</published><updated>2009-06-23T22:08:20.651+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><category scheme='http://www.blogger.com/atom/ns#' term='failure'/><title type='text'>Paying the Price of Ignorance</title><content type='html'>Once upon a time, there lived a community in a small town by a river. There were not many of them. The whole community was about 60 persons. Everyone lived in harmony with each other. There was little to fight about.&lt;br /&gt;&lt;br /&gt;As good as it sounds, the town was not without its problems. It had a poor drainage system. Couple that with how close it was to the river, they had major flood problems whenever the rain kept pouring.&lt;br /&gt;&lt;br /&gt;For generations, the people were taught that the flood could not be avoided. The only thing they could ever do was to prevent as much water as possible from going into their houses. And so it was, for many generations. And it worked. At least, before it reached the size of 60 persons.&lt;br /&gt;&lt;br /&gt;When there were more people, the deficiency of the poor drainage was even more obvious. There were more trash and houses. The drainage system couldn't keep up. Although raised as a problem every now and then by the community, there were never actions taken to address the drainage system problem. To those who ran the town, revamping the drainage system was too much cost. The flood never happened often enough to justify and every time it happened, the people were able to prevent too much damage.&lt;br /&gt;&lt;br /&gt;To some of them, the ever growing population was enough to keep the damages low. People just needed to help each other when there was a flood. Also, the post-flood town rebuilding works really kept them too busy to do anything else.&lt;br /&gt;&lt;br /&gt;And that remained for many many years. If there was one thing constant about the town, it would be the way they recovered from the floods. The total damages rose higher and higher as the population grew. The recovery time took longer and longer. Year by year, they got busier and busier.&lt;br /&gt;&lt;br /&gt;In short, everyone in the town was minimizing the burn, but never took time to take out the flames. In the end, they got incinerated.&lt;br /&gt;&lt;br /&gt;I think, things would've turned out better if:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The mayor had admitted the deficiency...&lt;/li&gt;&lt;li&gt;and invested early on for a better drainage system, before it became a huge undertaking.&lt;/li&gt;&lt;li&gt;The people didn't believe that orchestrated water scooping activities as a testaments to their unbelievable team work..&lt;/li&gt;&lt;li&gt;and put their efforts to innovative solutions to the root cause of the problem, not its symptoms.&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-7361167252655709921?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/7361167252655709921/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=7361167252655709921' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7361167252655709921'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7361167252655709921'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/06/paying-price-of-ignorance.html' title='Paying the Price of Ignorance'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-4426386634819451946</id><published>2009-06-11T23:15:00.002+08:00</published><updated>2009-06-13T12:03:42.359+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><category scheme='http://www.blogger.com/atom/ns#' term='leadership'/><title type='text'>Live and die by decisions</title><content type='html'>To some of us, making decisions is probably one of the hardest things we have to do. I guess it's fair to say that we often want to make the best decisions, and if possible, to not be accountable for the undesired consequences. But I find it ironic, because I see decisions as creating consequences. When a decision is made, the one who made it is accountable for the consequences, be it good or bad. That's because he asked for a change the moment he decided, and it's his change, really.&lt;br /&gt;&lt;br /&gt;Everyone has to make decisions.&lt;br /&gt;&lt;br /&gt;I'm not a decision making guru. I have not made half a billion of decisions. But I've been making some and I've been exchanging a lot of thoughts with people who make a lot of decisions. In all honesty, the methods are crap. Not because they don't work. They probably work very well for those who shared with me their techniques. I think they are crap because everyone makes decisions differently. &lt;br /&gt;&lt;br /&gt;Some people make decisions based on a thought process. Some make them based on experience. Some based on hunches. There are probably a lot more... but the methods are not what I'm writing about. I'm writing about what happens after decisions are made.&lt;br /&gt;&lt;br /&gt;When you're asked to make a decision, know this:&lt;br /&gt;&lt;br /&gt;Most of the time, it does not have to be immediate, but when you make it, it has to be firm. The last thing you want to show your people is that you're an indecisive decision maker. When you make a decision, you set the motion. Everything starts to roll based on what you said. It's a terrible thing to do to tell the motion to stop, rollback so your people could be in for another set back. When you feel that your decision is right and you have justifications to yourself why it is right, stick with it.&lt;br /&gt;&lt;br /&gt;Don't doubt yourself every 5 minutes and change the decision every 30. The people who need your decision is not a text editor. You don't type, reread, and backspace all you want. When you have decided, stick with it, and make the best out of it. But of course, you should never be...&lt;br /&gt;&lt;br /&gt;A stubborn ass (the animal, but feel free to interpret with the other meaning). Decisions can be wrong. I think it's important to keep an open mind when someone challenges your decision. In fact, I think it's awesome when someone challenges your decision. &lt;br /&gt;&lt;br /&gt;I can understand how some of us might feel 'mocked' or disrespected when our decisions are challenged. But think about it in another way. After we have made a decision, can we ever be sure that our justifications for it is the only truth? My take: No. Never.&lt;br /&gt;&lt;br /&gt;To me, the only way I can be more confident of my justifications is by them surviving challenges. &lt;br /&gt;&lt;br /&gt;But of course, you can't always be wrong. Sure, you've probably made some bad ones, but my observation tells me that if you're smart enough that you're asked to decide on certain things, you will never always make the wrong ones. When your decisions are challenged, communicate properly and let them know your justifications. Feel free to let those who challenge you know why you think they are wrong, if you think they are wrong. Like you and I, they need to make mistakes to learn. If they never make any, they are not smarter than the day they are right.&lt;br /&gt;&lt;br /&gt;Of course, if you made a bad decision, the best thing you can do is to admit it. Admitting a wrong decision is not about saying you're incompetent of deciding. Admitting a wrong decision allows its corrections. If you're making a decision for your team, it also sends a stark reminder to your team members that anyone can make mistakes. To some teams, it makes the leader more 'human'.. and being human, that means he's more approachable and also someone they can challenge. &lt;br /&gt;&lt;br /&gt;Ultimately, that results in better decisions.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-4426386634819451946?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/4426386634819451946/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=4426386634819451946' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4426386634819451946'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4426386634819451946'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/06/live-and-die-by-decisions.html' title='Live and die by decisions'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-1834190847831800212</id><published>2009-05-17T11:57:00.007+08:00</published><updated>2009-05-17T12:35:37.695+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='best practices'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='jquery'/><title type='text'>Things to remember when writing dynamic Confluence macros</title><content type='html'>When writing macros in Confluence that rely heavily on JavaScript, I think it's good to always have a list of do's and don'ts. At least, I have. &lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Note&lt;/strong&gt;: This is a product specific application of &lt;a href="http://nuhk.blogspot.com/2008/12/cleaner-javascript-with-jquery.html"&gt;Cleaner JavaScript with jQuery&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Here's a part of my list (which exists only in my mind and it's quite long, so I'll just name a few that I think are most important).&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;1. Use the jQuery alias '$'&lt;/h2&gt;&lt;br /&gt;Because &lt;tt&gt;jQuery&lt;/tt&gt; is 5 bytes longer than &lt;tt&gt;$&lt;/tt&gt; and we want to keep our content small. '&lt;tt&gt;$&lt;/tt&gt;' is safe to use if you do it correctly. For instance:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;jQuery(function($) {&lt;br /&gt;  // '$' will definitely refer to jQuery anywhere inside this method and&lt;br /&gt;  // you don't have to worry about JS libs like Prototype screwing you &lt;br /&gt;  // up unexpectedly.&lt;br /&gt;});&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;2. Don't use element IDs&lt;/h2&gt;&lt;br /&gt;Because macros can be included in a page more than once and there isn't a reliable mechanism available to a macro that helps to guarantee ID uniqueness. Use CSS classes to pick up instances of your macro. For instance:&lt;br /&gt;&lt;br /&gt;In a macro that generates the following HTML:&lt;br /&gt;&lt;pre&gt;&amp;lt;div class=&amp;quot;mymacro&amp;quot;&amp;gt;&lt;br /&gt;  &amp;lt;!-- New functionality --&amp;gt;  &lt;br /&gt;&amp;lt;/div&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;In a static JS resource required somewhere&lt;/strong&gt;&lt;br /&gt;&lt;pre&gt;jQuery(function($) {&lt;br /&gt;   $(&amp;quot;div.mymacro&amp;quot;).each(function() {&lt;br /&gt;     // does something&lt;br /&gt;   });&lt;br /&gt;});&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If implemented right, you don't have to worry about element ID uniqueness or &amp;quot;unexpected&amp;quot; scenarios that can cause duplicate IDs like the usage of &lt;tt&gt;{include}&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;With that approach, it's much less likely that instances of your macro will interfere with each other.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;3. Avoid the use of inline JS&lt;/h2&gt;&lt;br /&gt;Because things like this doesn't look so nice.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;a class=&amp;quot;worldPieceInAClick&amp;quot; href=&amp;quot;#&amp;quot; onclick=&amp;quot;javascript:foobarThatReturnsFalse()&amp;quot;&amp;gt;Print more money.&amp;lt;/a&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In the absence of JavaScript (like when your page is emailed), the link should do nothing. That can be done by doing something like the below (based on the code above).&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;a class=&amp;quot;worldPieceInAClick&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;Print more money.&amp;lt;/a&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;In a static JS resource somewhere&lt;/strong&gt;&lt;br /&gt;&lt;pre&gt;jQuery(function($) {&lt;br /&gt;  $(&amp;quot;a.worldPieceInAClick&amp;quot;).click(function() {&lt;br /&gt;    // Do something fun?&lt;br /&gt;    return false;&lt;br /&gt;  });&lt;br /&gt;});&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If the static JS resource is not available, clicking the link is a no-op.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;4. JavaScript needs i18n love too.&lt;/h2&gt;&lt;br /&gt;Have we seen hardcoded user-viewable messages like this?&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;In a static JS resource somewhere&lt;/strong&gt;&lt;br /&gt;&lt;pre&gt;alert('Uh oh, something is wrong. Duck.');&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;With code like that, the user won't be aware that there is something wrong if he can't read English. But the problem is, static JS resources are not generated using Velocity, so it's impossible to have a localized version of it? Not really.&lt;br /&gt;&lt;br /&gt;Tip: JavaScript can read anything in the DOM. So:&lt;br /&gt;&lt;br /&gt;The better way is this:&lt;br /&gt;&lt;pre&gt;&amp;lt;div class=&amp;quot;mymacro&amp;quot;&amp;gt;&lt;br /&gt;  &amp;lt;fieldset class=&amp;quot;hidden&amp;quot;&amp;gt;&lt;br /&gt;    &amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;somethingiswrong&amp;quot; value=&amp;quot;$i18n.getText('somethingiswrong')&amp;quot; class=&amp;quot;mymacro-i18n&amp;quot;&amp;gt;&lt;br /&gt;  &amp;lt;/fieldset&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;In a static JS resource somewhere&lt;/strong&gt;&lt;br /&gt;&lt;pre&gt;jQuery(function($) {&lt;br /&gt;  mymacro = {&lt;br /&gt;    getI18nMessages : function(myMacroDiv) {&lt;br /&gt;      var i18nMsg = {};&lt;br /&gt;      myMacroDiv.children(&amp;quot;fieldset.hidden&amp;quot;).children(&amp;quot;input.mymacro-i18n&amp;quot;).each(function() {&lt;br /&gt;        i18nMsg[this.name] = this.value;&lt;br /&gt;      });&lt;br /&gt;&lt;br /&gt;      return i18nMsg;&lt;br /&gt;    }&lt;br /&gt;  };&lt;br /&gt;&lt;br /&gt;  $(&amp;quot;div.mymacro&amp;quot;).each(function() {&lt;br /&gt;    var myMacroDiv = $(this);&lt;br /&gt;    myMacroDiv.find(&amp;quot;a.somethingiswrong&amp;quot;).click(function() {&lt;br /&gt;      var i18nMsg = mymacro.getI18nMessages(myMacroDiv);&lt;br /&gt;      alert(i18nMsg[&amp;quot;somethingiswrong&amp;quot;]);&lt;br /&gt;    });&lt;br /&gt;  });&lt;br /&gt;})&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;5. Think about how your macro will look like in PDF, HTML export, Email and RSS&lt;/h2&gt;&lt;br /&gt;For instance, the hidden loading icon will probably show in RSS.. and in RSS, you don't have CSS (or you have CSS with broken arms and legs).&lt;br /&gt;&lt;br /&gt;For trivial macros, I like to have flags that decide whether or not to generate those &amp;quot;active&amp;quot; elements. For more complex ones, I prefer to have a &amp;quot;static&amp;quot; template.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-1834190847831800212?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/1834190847831800212/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=1834190847831800212' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/1834190847831800212'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/1834190847831800212'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/05/things-to-remember-when-writing-dynamic.html' title='Things to remember when writing dynamic Confluence macros'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-5131535378541586378</id><published>2009-04-26T10:36:00.016+08:00</published><updated>2009-04-26T23:48:24.550+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='career'/><title type='text'>Surviving the resume scan</title><content type='html'>While I understand the notion of starting a post with &amp;quot;It has been a while since&amp;quot;, I really can't escape from it. I've been looking for something to blame for that, but everywhere I look I just see myself. So rather than to start a post with a similar phrase next time, I'll just say &amp;quot;Read the next paragraph&amp;quot;, maybe. Since that's for the next, I'm going to start things off. So, it has been quite a while...&lt;br /&gt;&lt;br /&gt;Work is always busy for me. But these recent weeks, hectic would be an understatement. There were surges of meetings and interviews that I had to go to that took a lot of time. It's very typical for a meeting or an interview to take up to an hour or two, depending on the agenda or the position that we're looking to fill.&lt;br /&gt;&lt;br /&gt;Before interviews, resumes have to be read. Certainly, that is not a specialized task that one does 8 hours a day. It is most probably an additional responsibility of the interviewer. &lt;br /&gt;&lt;br /&gt;Despite the squeeze, resume scanning is crucial. It can determine whether the company gets/misses the correct person for the job or if the interview is going to be a waste of time. Unfortunately, depending on the times, there can be a flood of resumes that have to be scanned. When that happens, your resume will have limited time to tell your potential employer why you're the guy/girl for the job. &lt;br /&gt;&lt;br /&gt;In other words, your resume will only get 5 minutes of the interviewer's time. Maybe less.&lt;br /&gt;&lt;br /&gt;So, to increase your chances in what I would call the 5-minute window of opportunity, you need to help the interviewer to help you. That's pretty much like what Jerry Maguire said. So you have 5 minutes. What should you do?&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Have a proper email address&lt;/h3&gt;&lt;br /&gt;Email addresses like &lt;tt&gt;funboy23 at hotmail dot com&lt;/tt&gt;, &lt;tt&gt;ilovedavidarchuleta at yahoo dot com&lt;/tt&gt;, &lt;tt&gt;darkmayor at gmail dot com&lt;/tt&gt;, &lt;tt&gt;c0d1ingu7r at hotmail dot com&lt;/tt&gt; (my apologies if any of the mentioned addresses are real) look really bad. Why? Because that's almost like walking into someone's wedding with shorts and holding a bottle of beer smelling like a typical public Malaysian toilet. A proper email address tells your potential employer that you're serious. That's important because your potential employer will certainly be.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Move less relevant details elsewhere (or remove it)&lt;/h3&gt;&lt;br /&gt;Some examples of less relevant details would be your looks, gender, race, home address and hobby. When an interviewer is scanning resumes, he or she is looking for someone that can do the job. How you look, whether you're a male or a female or what you like to do in your free time doesn't tell the interviewer that. In fact, I think in some countries, it's not recommended to have those details in your resume as it might cause some discrimination.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Recent jobs with achievements&lt;/h3&gt;&lt;br /&gt;Start with the current employment (if any) and the most recent ones dating as far back as appropriate. Unfortunately, there's no hard and fast rule how many years you should go back. However, from what I've seen so far, three to four years is a good enough limit. Most interviewers look at the employment history to see if you're doing things that are relevant to the position they are hiring for in the recent years. &lt;br /&gt;&lt;br /&gt;By the way, if software engineer is the position, my personal take is that  anything that you've done over four years ago should be irrelevant by now.&lt;br /&gt;&lt;br /&gt;It's also important to highlight your achievements in the list. They don't need to be measurable. But being able to tell your potential employer about your achievements is a good thing because they can gauge how competitive you are in the things that you did that are relevant to the position.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Skills set&lt;/h3&gt;&lt;br /&gt;Again, only list the things that are relevant. I think most of us have a very long list of skills, but in a resume, you should only list those that are relevant to the position you're applying for. While it may be true that some of us have unbelievable Excel hacking skills or insane Word pretty-fying capabilities, they are &lt;em&gt;not&lt;/em&gt; related to the position. It scares me sometimes to see those skills listed in a resume. Whenever I see those skills, two thoughts will usually come across my mind.&lt;br /&gt;&lt;br /&gt;1. Is the applicant listing those skills just for the sake of filling up the 10-skills list in a resume template?&lt;br /&gt;2. Does the applicant know what he/she is applying for?&lt;br /&gt;&lt;br /&gt;Seriously, having a long list of skills does not mean you are immediately in the impressive list. Often, it's better to have a short list of skills that are relevant. Long lists with irrelevant ones actually look bad.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Referrals&lt;/h3&gt;&lt;br /&gt;I doubt if referrals are ever contacted in the 5-minute resume scan. The information could be used post-interview, however. But I don't think that happens often at all. Personally, I don't think referrals paint an accurate picture of the applicant.&lt;br /&gt;&lt;br /&gt;Most referrals are former supervisors or bosses. They are in one family &amp;mdash; people that used to work with you in one of your jobs. While I don't think they'll lie, I believe that they are unlikely to provide the complete picture of you that they have in their minds. For many reasons.&lt;br /&gt;&lt;br /&gt;If you have to have referrals, try to get &lt;em&gt;customer&lt;/em&gt; referrals.&lt;br /&gt;&lt;br /&gt;Having a short and accurate resume increases your chances of being selected. It will save your time and the interviewers'. You get good karma from it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-5131535378541586378?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/5131535378541586378/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=5131535378541586378' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/5131535378541586378'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/5131535378541586378'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/04/surviving-resume-scan.html' title='Surviving the resume scan'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-7811654528654511639</id><published>2009-02-14T09:21:00.003+08:00</published><updated>2009-02-14T09:25:33.281+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>Happy 1234567890!</title><content type='html'>&lt;a href="http://entertainment.slashdot.org/article.pl?sid=09/02/13/1534240"&gt;1234567890 seconds since Epoch!&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://technology.timesonline.co.uk/tol/news/tech_and_web/article5434062.ece"&gt;Zune didn't break, this time&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-7811654528654511639?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/7811654528654511639/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=7811654528654511639' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7811654528654511639'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7811654528654511639'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/02/happy-1234567890.html' title='Happy 1234567890!'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-4387897381926186334</id><published>2009-02-09T14:35:00.003+08:00</published><updated>2009-02-09T14:55:54.223+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='best practices'/><category scheme='http://www.blogger.com/atom/ns#' term='process'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>David's 3-step weekend train wreck prevention</title><content type='html'>If you found yourself having gone through really hectic weekends on some random occasions of retrospection, you'd probably think you'd rather be at work if you have to go through another one. You might also be thinking that you're insane having thought of that, but actually, you're quite sane.&lt;br /&gt;&lt;br /&gt;Most of the sane people that I know (yes, fortunately, I do know some insane people &amp;mdash; they tell me I'm not) want to have uneventful weekends. Uneventful here doesn't mean that you lie there on a couch waiting for a bum rot. Uneventful means you don't get unexpected events that stress you out in what is supposed to be a &amp;quot;stress recuperation&amp;quot; period.&lt;br /&gt;&lt;br /&gt;Here are some events which I categorize as &amp;quot;unexpected&amp;quot;.&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Breakfast, lunch or dinner invitations that can only be attended if I can manipulate time-space.&lt;/li&gt;&lt;li&gt;Oh-shit-I-forgot-about-X panic scrambles.&lt;/li&gt;&lt;li&gt;Random tasks that I only thought of doing when I look at myself in the mirror in the morning (haircut, etc).&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;These are the things that will burn your weekends. If you want your weekends to be &amp;quot;uneventful&amp;quot;, you have to minimize unexpected events. Here is my 3-step approach to preventing a train wreck weekend.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;1. Identify&lt;/strong&gt;&lt;br /&gt;Before your weekend begins, you need to think about what you want to get out of it. You need to identify your objectives. It doesn't matter if your objectives are too weird to be even thought about (like watching TV in the nude), you really need to identify them. For me, my objective is simple:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Maximize slack time &amp;mdash; Means I want to have as much free time as possible in between tasks and the delays on them do not have any significant effects on my next weekend.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;With objectives defined, you need to identify what your blockers are. Blockers are the things that prevent you from getting to your objectives. Taking my usual objective above, my blockers are usually:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Appointments &amp;mdash; They don't die. They just get postponed.&lt;/li&gt;&lt;li&gt;Chores &amp;mdash; These are elephants. They don't forget about you. When the time is right, they'll stomp on your face.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Once your objectives and blockers are identified, you need to...&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;2. Plan&lt;/strong&gt;&lt;br /&gt;To me, planning is an overhead that can &lt;em&gt;only&lt;/em&gt; be done on future events that you have a good idea of their happening. If you're planning for unexpected events, you need to stop drinking. Period. Planning for unexpected events is a mistake. If you're planning for something that can't be planned, you're screwed.&lt;br /&gt;&lt;br /&gt;So, with that in mind, what can we plan for? Well, bring up your calendar and ask yourself these questions:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;What are the things that will happen throughout the weekend? Are they plotted on the calendar? If they are not, do so now.&lt;/li&gt;&lt;li&gt;Look at your blockers list. Are they also on the calendar? No? Do it.&lt;/li&gt;&lt;li&gt;Does your calendar now say you have to manipulate space time? It does? Then do either of postponing or victimize, whichever applicable. If you postpone, make sure it's on your calendar.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;Now that you have a rough plan for your weekend, it's time to think about its optimization. How I usually go about this is by location. Which events will happen at nearby areas? It's probably better to block a period of time for events that will happen in nearby areas. For instance, if I need to be at the dentist at 11:00 a.m. at a place which is about 5 miles away from where I am going to meet my insurance agent later at about 2:00 p.m. and I need to have lunch in between, it's probably better to group these events. Why not just:&lt;br /&gt;&lt;br /&gt;Reschedule the meeting with my insurance agent to lunch time, at the same area where I am going to see my dentist.&lt;br /&gt;&lt;br /&gt;At this point, you may be thinking that rescheduling is an anti-plan. Yes, I admit that rescheduling means that the plan is screwed up, but an early reschedule has almost the same meaning as a far-from-release-date-software-refactoring. Good software gets refactored all the time and that allows for better software. For a plan, an early reschedule allows for better time usage (better plan).&lt;br /&gt;&lt;br /&gt;When you ask for a reschedule very early on, you give enough time for the affected people to reorganize their plans. When there is enough time for involved parties to change (plan-wise), time will not be wasted and priorities can be met. It saves on the profanity factor as well.&lt;br /&gt;&lt;br /&gt;Now, after you have optimized your plan, you can see where you can spend time achieving your objectives. But wait, it seems now that the objectives have the lowest priority? &lt;br /&gt;&lt;br /&gt;Well, actually, no. They're still at the highest. In any effort to achieve certain objectives, there will be blockers to overcome. If you ignore your blockers and just go straight for the objectives, your view on your objectives is tainted. The reason behind that is when you have &amp;quot;achieved&amp;quot; your objectives by cutting certain corners or efforts, what you have achieved is a subset of what you have set out to. That's why I don't consider achieving objectives by ignoring everything else is really achieving objectives.&lt;br /&gt;&lt;br /&gt;Cool. There's a plan, now what?&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;3. Stick&lt;/strong&gt;&lt;br /&gt;I think, this is the hardest part for most of us &amp;mdash; To stick to the plan.&lt;br /&gt;&lt;br /&gt;It's quite common that unexpected events happen in your weekends and that you actually &lt;em&gt;want&lt;/em&gt; to attend to them. For instance, a sudden invitation to go for some drinks on a Saturday night. If you have the buffer for it, I suggest that you go. But if you don't (the time and place of the invitation collides with what you have on in that period), I suggest that you take a rain check on it.&lt;br /&gt;&lt;br /&gt;It's a bit harsh, isn't it? Well, no, again. If you really want to be participating in that Saturday night hangover-in-the-making session, and you think that whatever you have at that time can be victimized, the objectives that you've set for that week are wrong. But didn't I say that unexpected things can't be planned? Yes, I did. However, you can have a backup plan.&lt;br /&gt;&lt;br /&gt;If you really want attend the Saturday night session, you must anticipate for it. Instead of plotting one concrete thing for the period of time that you anticipate the drinking session will be on, plot two. For me, I'd first plot a period of time on Saturday night for meeting friends up. Then I'd plot one more thing that I have full control of that I know I can do anytime &lt;em&gt;without affecting anyone&lt;/em&gt;.&lt;br /&gt;&lt;br /&gt;For instance, I can make any of the following a backup plan:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Finish reading XYZ.&lt;/li&gt;&lt;li&gt;Clean my room.&lt;/li&gt;&lt;li&gt;Gather receipts for the coming income tax scramble.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;If drinking does not happen, I can do something with that time &amp;mdash; it doesn't turn into yet another unexpected period of time available for something else.&lt;br /&gt;&lt;br /&gt;Of course, you don't want too many anticipations to be in your plan. Otherwise, it's as good as nothing.&lt;br /&gt;&lt;br /&gt;So there you have it, David's 3-step weekend train wreck prevention.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-4387897381926186334?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/4387897381926186334/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=4387897381926186334' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4387897381926186334'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4387897381926186334'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/02/davids-3-step-weekend-train-wreck.html' title='David&apos;s 3-step weekend train wreck prevention'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-4340025505007644970</id><published>2009-01-29T02:24:00.006+08:00</published><updated>2009-01-29T02:52:00.434+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='wtf'/><title type='text'>Why infrastructure is gold</title><content type='html'>As part of my continuous efforts to settle my imaginary blog debt, I have decided that for the rest of the year, whenever I can, to write blogs about events happening to and around me as soon as they happen.&lt;br /&gt;&lt;br /&gt;I returned a call to a friend last night. This friend, who I will call Winston from this point onwards, is one of those friends who you know, that when he looks for you, it is usually of something urgent that requires your assistance. &lt;br /&gt;&lt;br /&gt;Winston is now a consultant in his company. It has been long since his last call, but since it's the Chinese New Year holidays now and most of everybody I know would be taking advantage of it, I returned his call with the thought that there must be shit that has happened that I could help with.&lt;br /&gt;&lt;br /&gt;So I rang up, and I got to him. His question to me was:&lt;br /&gt;&lt;br /&gt;&amp;quot;Can I ask you a Linux question?&amp;quot;&lt;br /&gt;&lt;br /&gt;At an instant, I felt a surge of fury. I did not know why then, but now, when I think about it, it must've been caused by one or more of the following factors:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://acronyms.thefreedictionary.com/DATAJA"&gt;DATAJA&lt;/a&gt;&lt;/li&gt;&lt;li&gt;I'm curious why I am suddenly &lt;a href="http://tldp.org/"&gt;TLDP&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.apple.com"&gt;I'm already in happy land&lt;/a&gt;, it gives me the right to forget everything Linux&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Fortunately, I was able to control the anger and didn't manage to shower Winston with much love. So, I listened and the real question was (in more or less exact words):&lt;br /&gt;&lt;br /&gt;&amp;quot;Help! My CVS users can't check out files and the error related to it is sufficient disk space. I've freed up some space in all related partitions, but it still doesn't work. Any ideas?&amp;quot;&lt;br /&gt;&lt;br /&gt;The processing model I have in my brain for this kind of situations is a two-part:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Do I care?&lt;/li&gt;&lt;li&gt;If I care, ask for more information. If I don't, offer a generic piece of crap advice and look like I do.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;And so I told Winston, &amp;quot;My, that's a strange problem. Probably worth to take a backup of it and rebuild the server&amp;quot;. Of course, I am quite sure that the box he is using as the CVS server has no meaningful purposes other than the one that doesn't currently work.&lt;br /&gt;&lt;br /&gt;As you would imagine, no one is going to take that crap advice immediately. So the conversation prolonged and in my last-ditch effort, I told Winston to transfer the CVS server to another box so that in the duration he rebuilds the server, his users are not completely blocked... and this was where the point of hell fury started - He told me even more stuff that I didn't care.&lt;br /&gt;&lt;br /&gt;He told me that investing on another box can't be justified. Then he went on rambling that his company is no longer the Java-only company that relies on the CVS. He can't afford too much time in that too. He'll probably pass this back to his colleague.&lt;br /&gt;&lt;br /&gt;So I just told Winston that if I were in his shoes, I'd rebuild the server. End of story. &lt;br /&gt;&lt;br /&gt;I would've questioned his generosity in providing me with more information if he wasn't a friend (or someone who I didn't find the relevancy of ending on good terms with).&lt;br /&gt;&lt;br /&gt;What would my questions be?&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Why on earth is a consultant doing a sysadmin's job?&lt;/li&gt;&lt;li&gt;And being totally clueless about it?&lt;/li&gt;&lt;li&gt;And being defensive for his incompetencies?&lt;/li&gt;&lt;li&gt;And assuming the CVS is only for Java??&lt;/li&gt;&lt;li&gt;And doing the consulting shit on me when I told him to transfer the CVS to another server?&lt;/li&gt;&lt;li&gt;And who said anything about purchasing another box?&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;It's easy at this point to say that Winston is the problem. But that would be wrong. It's not his fault. If you look at the first question in the list above, the key is the answer to why a consultant is doing a sysadmin's job (and possibly fucking it up).&lt;br /&gt;&lt;br /&gt;The answer is simple: The infrastructure provided for cross-team support isn't strong. Winston's company does not have an internal support team. That's why Winston was screwing up.&lt;br /&gt;&lt;br /&gt;In contrast, I am now working in a company that has an excellent support team. Man, these guys are awesome! Not only do these guys free us up from internal maintenance, they also constantly upgrade our infrastructure so we are always supported to perform at our best. Without them, I think we'd be miles behind where we are now. My hat's off to them.&lt;br /&gt;&lt;br /&gt;So if you're running an IT-based company, especially in services, please please puhleeze setup a crack support team. They'll do your company a lot of good.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-4340025505007644970?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/4340025505007644970/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=4340025505007644970' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4340025505007644970'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4340025505007644970'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/01/why-infrastructure-is-gold.html' title='Why infrastructure is gold'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-8592523557449373184</id><published>2009-01-26T21:33:00.003+08:00</published><updated>2009-01-26T21:53:51.309+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='development career'/><title type='text'>Being a good developer</title><content type='html'>Every now and then I get this big question. Now, what I'm about to write does not indicate I am the best chest-beating developer ape out there, but it's just that it has come to a state where responding to that question has become unbearably repetitive. Instead of reiterating over and over again, I'll just write a blog post and redirect people here and hopefully, this blog post will be the ground-zero of a &lt;strike&gt;flame war&lt;/strike&gt; discussion.&lt;br /&gt;&lt;br /&gt;First, I have to congratulate you that you asked that question. That is, really, the first most important step. If you want to be a good developer, you need to know you want to be... and it's only people who ask that question that will successfully become good developers.&lt;br /&gt;&lt;br /&gt;There are many traits that a person should have to become a good developer, but I'll just list down the 5 that I think are most important.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;1. Passion&lt;/strong&gt;&lt;br /&gt;You have to want to do it. More often than not, the most obvious difference between the good and the average developer is the presence of passion in them. Development is a crazy task. When you're developing, you're creating tools that people will use in their work/personal lives. These are the people who have chosen your software after comparing it with others. They chose your software because they think it will help them with what they want to do.&lt;br /&gt;&lt;br /&gt;Now, for you, as the engineer, you have to care about your software. When you ship that software to your users, you're basically telling them that you're able to help with their problems. So, you really want the software to work as good as it can. If it doesn't, your users will be be stuck and you would have failed them. While it's true that there will always be bugs with software, it's not an excuse to be careless with it.&lt;br /&gt;&lt;br /&gt;Think about it this way. When software doesn't work, it's not immediately fixable. It's not like a spelling mistake which you can bring an eraser to (the root of the problem) and fix it on the spot. When it doesn't work, it usually means that the users will be stuck for a couple of months. If your users are depending on that software for a very important purposes, it will be a painful experience for both parties (one of them you).&lt;br /&gt;&lt;br /&gt;So, you really have to have the passion to write software great.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;2. You write tests&lt;/strong&gt;&lt;br /&gt;Testing is a very important part of development. I don't care who you are, but I can assure you that you can't be certain whatever you have written works until you have tested it. Simple as that. A good developer is one who will write tests to validate their own work. For instance, if you're writing a function that calculates the Fibonacci of X, you'll really want to write tests which validate that function (by examining its return value) with various arguments. Only then can you be sure that you're not writing stuff that doesn't work.&lt;br /&gt;&lt;br /&gt;But what if you have a dedicated testing team? Believe me, there were times I got so disappointed after hearing developers blaming the testing team for "unit test" kind of bugs in post-production event meetings. His argument implied that the testing team didn't do their jobs well enough. Here's the thing, testing teams are supposed to find tricky or edge-cases bugs. If they have to validate the developers' work at the "unit" level, who's going to cover the tricky bugs?&lt;br /&gt;&lt;br /&gt;As a good developer, you want to write tests to make sure that software at least work the way they are supposed to. You have to accept that testing is part of your responsibilities.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;3. You have little patience for things that suck&lt;/strong&gt;&lt;br /&gt;Unfortunately, no application code can be free from suckage. Look closer and you will definitely find code that is functional but dodgy. A good developer will be able to identify that and will remember it until the next engineering meeting. He or she will then bring this up with the hope that the fix will be included in the application's roadmap somewhere not too far down the line.&lt;br /&gt;&lt;br /&gt;So why is this important? Well, that's because good code promotes goodness stack-ability. What does that mean?&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Good code will have lesser bugs.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;That means less time is needed to fix it.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;That means there is more time to do something else with the code, like make it better.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;That means maintaining the code will be easier.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;That means it will be more likely that other developers would want to make it better.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;That means it will have even even lesser bugs.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;While there could be times when a number of roadmaps will have significant quantities of "refactoring" items, I think that will pay off in the long run.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;4. You dumb down the source.&lt;/strong&gt;&lt;br /&gt;A good developer solves complex problems in very simple ways (or the simplest ways possible). While software complexity can't be avoided (you're building a solution afterall), it can be minimized. From my experience, software complexity has a lot to do with the number of code paths in it - the number of if-else.&lt;br /&gt;&lt;br /&gt;A lot of development problems can be solved with minimal changes to the code. That's the easy way and it usually includes adding if-else to the code base at various places. While that might solve the problem at hand, it creates another. Due to that, the software will become more complex, harder to maintain and isn't as self-documenting.&lt;br /&gt;&lt;br /&gt;Let's take the problem of processing the 'A' in a set of A-Z and how a good developer might approach in solving it.&lt;br /&gt;&lt;br /&gt;Instead of looping through each element in the set and only doing the processing if the element being checked is 'A', it's actually simpler to just sort the set in ascending order and process the first element.&lt;br /&gt;&lt;br /&gt;A good developer should always seek to solve problems in the simplest, most elegant ways.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;5. A good developer communicates.&lt;/strong&gt;&lt;br /&gt;If you're in any serious development team, issue trackers, source code management systems, build servers, roadmaps and so forth should be familiar to you. But what do these tools have to do with communication? Well, they are all made for that - to communicate different aspects of development projects to the people involved. Being a good developer, you should make full use of them. For instance:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;If you notice someone committing a feature that is suppose to be in the next version, you should talk to the developer in question and figure out what is going on.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;If you notice that an issue was resolved without a fix version, you try to find out what it is and populate that information. If you can't, always talk to the issue owner about it.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;If you notice stuff (like IDE workspace files) getting checked into the source repository (and you know that they shouldn't exist there), you should go and talk to the other developer about it.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;If you find that the roadmap's timeline is impossible, you need to raise the problem/concern to your project manager.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;... and the list goes on. The point here is that as a good developer, you don't close your eyes on problems you noticed. You and every one in the team must be at full alert to cover each others' backs. If anyone is going to notice problems with development projects early, they will be the developers and that is when the problems are usually still fixable.&lt;br /&gt;&lt;br /&gt;So there you have it, the five things that I think a good developer should have.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-8592523557449373184?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/8592523557449373184/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=8592523557449373184' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8592523557449373184'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8592523557449373184'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/01/being-good-developer.html' title='Being a good developer'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-5487090972551748998</id><published>2009-01-25T12:09:00.005+08:00</published><updated>2009-01-25T12:41:04.673+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='user interfaces'/><category scheme='http://www.blogger.com/atom/ns#' term='usability'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><title type='text'>Email notifications + static web page resources = fail?</title><content type='html'>&lt;a href="http://nuhk.blogspot.com/2008/12/cleaner-javascript-with-jquery.html"&gt;Aren't non-inline JS and CSS good things&lt;/a&gt;? Well, yes, they are. Unfortunately, content with non-inline JS or CSS does not work properly in emails. Take a typical web application that emails users of changes to certain pages that are authored by users themselves. It is very likely that there is an option somewhere that says, &amp;quot;Include the whole page in notification emails&amp;quot;... and sure enough, the whole page, along with all of its links to the required static resources like CSS are included in a (HTML) notification email.&lt;br /&gt;&lt;br /&gt;Unfortunately, complex content like that usually break in emails and that's usually due to missing items in linked resources. For instance, CSS classes.&lt;br /&gt;&lt;br /&gt;Email (or to some, e-mail), this relic of technology just refuses to die. Perhaps, it is Utopia or perhaps it's just something so integral to us, just like how we like to use our mouths to eat. For whatever reasons keeping it alive, I have ceased to care.&lt;br /&gt;&lt;br /&gt;One of the biggest problems with static resources is that they are usually protected. Email clients won't know how to authenticate to the servers hosting those resources in order to download their contents. For instance:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;A protected image source --- &amp;lt;img src=&amp;quot;/blah/email.gif&amp;quot; /&amp;gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;A protected frame/iframe source --- &amp;lt;iframe src=&amp;quot;/blah/must&amp;quot; /&amp;gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;A protected CSS resource --- &amp;lt;link ref=&amp;quot;stylesheet&amp;quot; href=&amp;quot;/blah/die.css&amp;quot; /&amp;gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;A protected JS resource --- &amp;lt;script src=&amp;quot;/blah/horribly.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;The above are just some of the infinite number of ways how the rendering of web pages can be broken in emails.&lt;br /&gt;&lt;br /&gt;So, what can we do about it? Think different. Viewing web pages through email is like studying natural animal behavior from the safety of a zoo. Instead of trying to make it work, let's rethink the whole problem.&lt;br /&gt;&lt;br /&gt;Why do we want notifications? We want to know the changes or related mutating events that have happened since the last time we checked. Quickly, that. Does showing a broken web page in email help? Probably, if the viewer knows historically what was broken before and is able to do a manual version comparison in his head each time the page is viewed.&lt;br /&gt;&lt;br /&gt;So, to make it obvious, the purpose of notification is to highlight changes quickly.&lt;br /&gt;&lt;br /&gt;So, what can we do for complex content?&lt;br /&gt;&lt;br /&gt;Well, how about this? In notification emails, in place of the full page being shown (possibly broken):&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Show the change comment in an unmissable manner, like make it notoriously big. In case of no change comment, say something like &amp;quot;User has not specified change summary.&amp;quot;.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Diff#Unified_format"&gt;Show the changes in text&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Provide a private link that when clicked on, will open the user's default browser up and redirect the user to the changed page. The difference between a private link and a normal link is that a private link does not require authentication, and of course, is private to the email reader. The private link will also expire after a period of time.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;While what I'm suggesting might seem to be targeted at certain types of applications, I think this idea can be easily adapted to any system that make use of emails for notification purposes.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-5487090972551748998?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/5487090972551748998/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=5487090972551748998' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/5487090972551748998'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/5487090972551748998'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2009/01/email-notifications-static-web-page.html' title='Email notifications + static web page resources = fail?'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-4229305339367159107</id><published>2008-12-14T09:31:00.007+08:00</published><updated>2009-09-14T10:17:41.805+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='best practices'/><category scheme='http://www.blogger.com/atom/ns#' term='user interfaces'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='i18n'/><title type='text'>Cleaner JavaScript with jQuery</title><content type='html'>I've been doing some widget/portal-ish (which I'll call components from this point forward) JavaScript stuff lately. Along the way, I discovered a few common faults to those components in that context and learned how to fix them. Most of them boil down to:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;The uniqueness of element IDs in documents are overlooked. When developing components, it is very likely that it could be used more than once in a page. While generating elements with specific element IDs is straightforward, it might cause problems when the component is used multiple times in the same document.&lt;/li&gt;&lt;li&gt;Often, massive JavaScript and CSS are coded inline to the HTML elements. That's not ideal for performance... and to some degrees, even contribute to the problem mentioned earlier.&lt;/li&gt;&lt;li&gt;Internationalization doesn't exist.&lt;/li&gt;&lt;li&gt;Pollution of the global JavaScript namespace. In many circumstances, the objects that we have in the global scope can be reduced. There are components which try to improve that area by creating global JS objects with name space components. (e.g. &lt;tt&gt;com.foo.bar.mywidget&lt;/tt&gt;). However, I believe that the best way is to not have global objects if possible.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;So, what can we do about them?&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Solving 1&lt;/h2&gt;If element IDs are really needed (and that CSS classes for jQuery selection isn't fitting), assign them on DOM ready. For instance:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;jQuery(function($) {&lt;br /&gt;jQuery("form.myform").each(function(index) {&lt;br /&gt;jQuery(this).attr("id", index);&lt;br /&gt;});&lt;br /&gt;});&lt;/pre&gt;&lt;br /&gt;The code above will find all instances of the component (assuming that it generates &lt;tt&gt;&amp;lt;form class="myform"&amp;gt;&lt;/tt&gt; for each usage) and allows you to truly assign unique IDs to each instance of your component.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Solving 2&lt;/h2&gt;With the practice above, it is possible to remove JavaScript generation by components. All that is required for that is to generate elements which can be selected (by jQuery). A common technique is to generate elements with CSS classes assigned to them and use jQuery to select them based on the CSS classes. All JS initialization on elements in a document should be done on DOM ready as well, by registering hooks to jQuery.&lt;br /&gt;&lt;br /&gt;Event handling can also be initialized on DOM ready. Inline event handlers like the one below can be eliminated.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;input name="foobar" class="testClass" type="button" onclick="doSomething()" /&amp;gt;&lt;/pre&gt;&lt;br /&gt;A better way to do it would be something like this in a static JS linked from the page.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;jQuery(function($) {&lt;br /&gt;jQuery("input.testClass").each(function() {&lt;br /&gt;jQuery(this).click(function(event) {&lt;br /&gt;alert('I am clicked.');&lt;br /&gt;});&lt;br /&gt;});&lt;br /&gt;});&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Therefore, with hooks like that, it is possible to generate HTML that is &lt;em&gt;completely&lt;/em&gt; free of inline JS and CSS (which are &lt;em&gt;not cacheable&lt;/em&gt;). It's a big problem when a page is massive, especially with most things redundant. Putting JS and CSS in static resources makes them cacheable. When they are, browsers don't need to download so many bytes.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Solving 3&lt;/h2&gt;Have we ever seen something like this?&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;// When something is clicked, pressed or whatever&lt;br /&gt;alert('Oh hi, I just upgraded your RAM.');&lt;/pre&gt;&lt;br /&gt;That is not ideal because it prohibits internationalization. It's impossible to have localized messages for those events, even if presentation of the page is localized. Now, don't we all agree that inconsistency doesn't look too good?&lt;br /&gt;&lt;br /&gt;But wait, if we are going to internationalize JavaScript messages, don't we need to generate it, which makes static JS impossible? Well, not really. What we can do is this.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Put all our i18n messages in a fieldset, or anywhere we think is suitable that can be addressed by our JavaScript. I like to do this.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;fieldset class="hidden"&amp;gt;&lt;br /&gt;&amp;lt;input type="hidden" name="i18n-iupgradedyourram" value="Localized message" /&amp;gt;&lt;br /&gt;&amp;lt;/fieldset&amp;gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Then, read all of those i18n messages into a JS object like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;function getI18n() {&lt;br /&gt;var messages = {};&lt;br /&gt;jQuery("fieldset.hidden &amp;gt; input[name^='i18n-']").each(function() {&lt;br /&gt;messages[this.name] = this.value;&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;return messages;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;li&gt;Now, whenever I need the localized messages, I'll just call the function defined above and get them from the returned object, like this:  &lt;br /&gt;&lt;pre&gt;// When something is clicked, pressed or whatever&lt;br /&gt;var messages = getI18n();&lt;br /&gt;alert(messages["i18n-iupgradedyourram"]);&lt;/pre&gt;&lt;/li&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h2&gt;Solving 4&lt;/h2&gt;The idea behind this is that if JavaScript objects do not need to be globally addressable, why put them there? For instance, if you have a component called "ram-upgrader" and it creates a JavaScript object called "ramupgrader" to render itself, it's most likely that the object is only useful for ram-upgrader. Other parts of the page probably don't even need to know the JS object exists.&lt;br /&gt;&lt;br /&gt;So, instead of doing this:&lt;br /&gt;&lt;pre&gt;var myfunobject = {&lt;br /&gt;doSomething : function() { }&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;jQuery(function($) {&lt;br /&gt;myfunobject.doSomething();&lt;br /&gt;});&lt;/pre&gt;&lt;br /&gt;It would be better to do:&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;pre&gt;jQuery(function($) {&lt;br /&gt;var myfunobject = {&lt;br /&gt;doSomething : function() { }&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;myfunobject.doSomething();&lt;br /&gt;});&lt;/pre&gt;&lt;br /&gt;With those practice in place, it's easier to write JavaScript applications that are both cool and robust... and JavaScript code directly interacts with the user. Getting that right has a direct effect on what users feel about our applications.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-4229305339367159107?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/4229305339367159107/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=4229305339367159107' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4229305339367159107'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4229305339367159107'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2008/12/cleaner-javascript-with-jquery.html' title='Cleaner JavaScript with jQuery'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-1874122036987148500</id><published>2008-10-29T22:50:00.004+08:00</published><updated>2008-10-29T23:11:40.144+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='design patterns'/><category scheme='http://www.blogger.com/atom/ns#' term='architecture'/><category scheme='http://www.blogger.com/atom/ns#' term='wtf'/><title type='text'>The cost of growing your own lightweight library</title><content type='html'>So you've spent some time investigating some popular libraries you might want to use in your brand new application . However, you figured that based on the current requirements, you don't need all those functionalities in the libraries. They (the libraries) are, in a sense, overweight. It's like ordering the classic ribs from Tony Romas but only eating the mashed potato.&lt;br /&gt;&lt;br /&gt;... And you were right, at that period of time at least. I believe it is fair to say that most of us would come to the same conclusion. However, was what beyond that in our consideration when we concluded that way?&lt;br /&gt;&lt;br /&gt;Let me give you an example - JavaScript libraries. Most web applications are built with more static content than dynamic. That basically means JavaScript is a supplement for the user interfaces of those applications, not the core. So when those applications were being built, the authors probably did not need a full fledged JavaScript API like jQuery.&lt;br /&gt;&lt;br /&gt;Assuming you are building an application like those mentioned in the previous paragraph, you would probably need ten "lightweight" generic JavaScript functionalities that can be reused throughout the application. With that, you've grown your own JS library. From there, it's easy to see you might want to distribute the library. I mean, afterall, it would be useful for developers who are building similar applications like you.&lt;br /&gt;&lt;br /&gt;So, what's wrong with that?&lt;br /&gt;&lt;br /&gt;None and something. The answer differs depending on when you ask that question. If you ask that question when the library is still fresh dogfood, nothing is wrong. However, if you ask that question one year later, you'll see the problems.&lt;br /&gt;&lt;br /&gt;But before I elaborate, let me state the likely purpose you built the library for. The keywords here are "lightweight" and "fast" (be it application performance or development/testing time). With that, let's move on to the events outlined below.&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;You had a 1.0 milestone to hit and you built the web pages and the decorating theme.&lt;/li&gt;&lt;li&gt;As the application moved towards 1.0, you got more and more requests for dynamic elements in pages.&lt;/li&gt;&lt;li&gt;You had limited time to investigate in suitable libraries and reached a conclusion that they were too heavy based on the number of functions they had or the number of parameters you needed to pass into the methods you regularly use.&lt;/li&gt;&lt;li&gt;Based on the gathered information, it was logical to grow your own library (JS)... and so you have.&lt;/li&gt;&lt;li&gt;Application reached 1.0.&lt;/li&gt;&lt;li&gt;After 6 months, the application reached 1.5. With that, even more requests for dynamic elements in pages.&lt;/li&gt;&lt;li&gt;To meet the requirements, the home grown library was, well, expanded.&lt;/li&gt;&lt;li&gt;Then came 1.9 with its unimaginable amount of dynamic element requests. Then you took a step back and looked, and you realized:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;The lightweight library you've grown was no longer lightweight. The original purpose of the library became irrelevant.&lt;/li&gt;&lt;li&gt;The size of it rivals the size of other similar libraries, which have proven to be more mature since you last looked at it.&lt;/li&gt;&lt;li&gt;The home grown library is not as well structured as you would like (but that's a really nice way of saying "complete mess").&lt;/li&gt;&lt;li&gt;You've got too many pages in pre-1.9 to refactor if you want everything to be standardized into a new library.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;So you're stuck with your own library. Now, if it isn't grim enough, people who adopted your library will be stuck in the same manner, if not already.&lt;br /&gt;&lt;br /&gt;All that is just to say that if you need generic functionalities which the core programming language doesn't provide, use a library. Your concern is to build the application. Library builders do not share the same concern. They are mostly concerned with building a well structured API that is useful to many application builders like you. They dedicate their time to do that... and that, is not a luxury that you have as an application builder.&lt;br /&gt;&lt;br /&gt;So, leave specialized jobs to specialists.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-1874122036987148500?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/1874122036987148500/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=1874122036987148500' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/1874122036987148500'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/1874122036987148500'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2008/10/cost-of-growing-your-own-lightweight.html' title='The cost of growing your own lightweight library'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-4355892925243504490</id><published>2008-10-26T14:35:00.003+08:00</published><updated>2008-10-26T14:45:05.506+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='user interfaces'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><title type='text'>Flexi Blog Posts Plugin 1.0 released</title><content type='html'>Just recently, I've developed a macro for &lt;a href="http://www.atlassian.com/software/confluence/"&gt;Atlassian's Confluence&lt;/a&gt; which renders blog posts in a dynamic table. Dynamic really means:&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;You can sort the table by clicking on the headers.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;You can resize the table by dragging the table edges.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;The results shown in the table are generated in an AJAXy like manner -- no page refresh.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;... and the results are paged. So you can navigate them page-by-page, saving a lot of space on the screen.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The macro is now offered by my company (&lt;a href="http://www.customware.net"&gt;CustomWare&lt;/a&gt;) as an Open Source Confluence plugin. Interested? Head on to &lt;a href="http://www.customware.net/repository/display/AtlassianPlugins/Flexi+Blog+Posts+Plugin"&gt;the plugin's home page&lt;/a&gt;!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Hope you like it!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-4355892925243504490?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/4355892925243504490/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=4355892925243504490' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4355892925243504490'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4355892925243504490'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2008/10/flexi-blog-posts-plugin-10-released.html' title='Flexi Blog Posts Plugin 1.0 released'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-8677607492171646754</id><published>2008-10-18T12:49:00.001+08:00</published><updated>2008-10-29T23:16:21.123+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='humor'/><category scheme='http://www.blogger.com/atom/ns#' term='wtf'/><category scheme='http://www.blogger.com/atom/ns#' term='failure'/><title type='text'>Worse than failures, now in a banking app near you!</title><content type='html'>&lt;a href="http://blog.yclian.com/2008/10/guys-behind-new-maybank2u-please-read.html#comments"&gt;See&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-8677607492171646754?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/8677607492171646754/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=8677607492171646754' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8677607492171646754'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8677607492171646754'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2008/10/worse-than-failures-now-in-banking-app.html' title='Worse than failures, now in a banking app near you!'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-7393431357827705364</id><published>2008-08-07T21:54:00.003+08:00</published><updated>2008-08-07T22:25:27.030+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='user interfaces'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='i18n'/><title type='text'>It is painful to read HTML correctly</title><content type='html'>&lt;p&gt;Have you ever tried writing a program that needs to read any HTML and display it correctly? I guess most of us have. However, to those of you who have, have you tried making it work with localized HTML?&lt;/p&gt;&lt;p&gt;Most of us who wrote programs that read HTML programmatically would make them read it from a stream of bytes. For instance.&lt;/p&gt;&lt;p&gt;&lt;pre&gt;InputStream htmlContent = httpMethodBase.getResponseBodyAsStream();&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;That's fair because whatever that comes back should be treated as binary, even if the content type is text. Why? That's because to read the textual content correctly, you need to interpret it using a suitable character set (charset). Hence, the question below:&lt;/p&gt;&lt;p&gt;&lt;em&gt;If the content type is text/*, which charset should I use to interpret the stream?&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.w3.org/International/O-HTTP-charset"&gt;According to standards&lt;/a&gt;, localized HTML should specify the character set it is encoded with as a parameter to the &lt;code&gt;Content-Type&lt;/code&gt; header. For instance:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;Content-Type: text/html; charset=utf-8&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;If the parameter is not specified, the program reading the content should interpret it using ISO-8859-1. Sounds simple, so far. At least, until you go to &lt;a href="http://www.atlassian.jp/"&gt;this page&lt;/a&gt;. It's filled with Japanese text, and yet, my browser (FF3) seems to interpret it correctly. Is that the magic of the parameter specified with the &lt;code&gt;Content-Type&lt;/code&gt; header? You'll be surprised. The answer is &lt;em&gt;no&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;This is the output of Live HTTP Headers:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;http://www.atlassian.jp/&lt;br /&gt;&lt;br /&gt;GET / HTTP/1.1&lt;br /&gt;Host: www.atlassian.jp&lt;br /&gt;User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008072820 Firefox/3.0.1&lt;br /&gt;Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8&lt;br /&gt;Accept-Language: en-us,en;q=0.5&lt;br /&gt;Accept-Encoding: gzip,deflate&lt;br /&gt;Accept-Charset: UTF-8,*&lt;br /&gt;Keep-Alive: 300&lt;br /&gt;Connection: keep-alive&lt;br /&gt;&lt;br /&gt;HTTP/1.1 200 OK&lt;br /&gt;Date: Thu, 07 Aug 2008 02:54:02 GMT&lt;br /&gt;Server: Apache&lt;br /&gt;Last-Modified: Fri, 20 Jun 2008 08:23:53 GMT&lt;br /&gt;Etag: "a4006-33a9-45014d069b840"&lt;br /&gt;Accept-Ranges: bytes&lt;br /&gt;Content-Length: 13225&lt;br /&gt;Content-Type: text/html&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Cookie removed from request headers.&lt;/p&gt;&lt;p&gt;If you look at the last line in the response captured by Live HTTP Headers, there is no charset parameter. So, how did the browser do it? The magic lies in a meta tag in the response below.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&amp;lt;html lang=ja xml:lang=&amp;quot;en&amp;quot; xmlns=&amp;quot;http://www.w3.org/1999/xhtml&amp;quot;&amp;gt;&lt;br /&gt;&amp;lt;head&amp;gt;&lt;br /&gt;&amp;lt;title&amp;gt;アトラシアン ジャパン - JIRA：画期的な課題トラッキング! CONFLUENCE：エンタープライズ ウィキ&amp;lt;/title&amp;gt;&lt;br /&gt;&amp;lt;meta http-equiv=&amp;quot;Content-Type&amp;quot; content=&amp;quot;text/html; charset=shift_jis&amp;quot;&amp;gt;&lt;br /&gt;&amp;lt;!-- Snip --&amp;gt;&lt;/pre&gt;&lt;p&gt;Apparently, this method is &lt;em&gt;preferred&lt;/em&gt; because if the HTML is saved as a local file and opened by the web browser again, its representation is intact – you'll still see the page as it is intended to be seen. That's fine.. until you consider how you should read the HTML correctly, programmatically.&lt;/p&gt;&lt;p&gt;Gathering from the pages I've read about this, to read the HTML correctly, you need to.&lt;/p&gt;&lt;p&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Check the charset parameter in the &lt;code&gt;Content-Type&lt;/code&gt; header.&lt;br /&gt;&lt;br /&gt;Note that if the charset parameter exists, you can skip the following step and assume that the charset specified is the correct one to use to decode the data. That's because standards say it is the one "true" value if it doesn't agree with the one in the meta tag.&lt;/li&gt;&lt;li&gt;If it doesn't exist, check the charset specified by the relevant meta tag.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;/p&gt;&lt;p&gt;To be able to do step 2, you'll need to read the HTML. Fine? Well, which charset should be used to read it? ISO-8859-1? Seems like you can't. ISO-8859-1 can't decode the HTML correctly. Just try browsing to &lt;a href="http://www.atlassian.jp/"&gt;the example page&lt;/a&gt; and view it with the ISO-8859-1 encoding. You'll see gibberish text.&lt;/p&gt;&lt;p&gt;Just before 2, you have two options:&lt;/p&gt;&lt;p&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Read it using ISO-8859-1 and hope for the best.&lt;/li&gt;&lt;li&gt;Read it using a user-specified encoding, and still hoping for the best.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;/p&gt;&lt;p&gt;Either way can't guarantee that you'll be reading the HTML correctly... and that sucks.&lt;/p&gt;&lt;p&gt;Reading HTML shouldn't be this complex. I really hope it is something that I have missed that is not the usage of a specialized HTML parsing library.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-7393431357827705364?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/7393431357827705364/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=7393431357827705364' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7393431357827705364'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7393431357827705364'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2008/08/it-is-painful-to-read-html-correctly.html' title='It is painful to read HTML correctly'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-2764861176521147504</id><published>2008-07-31T20:25:00.008+08:00</published><updated>2008-08-05T23:35:05.798+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='best practices'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='i18n'/><category scheme='http://www.blogger.com/atom/ns#' term='wtf'/><title type='text'>Drowning in worse-than-failures. This time, i18n.</title><content type='html'>Have you ever seen code that accomplishes i18n by directly hardcoding the text into the source? Have not? Look at this.&lt;br /&gt;&lt;pre&gt;httpServletResponse.getWriter().write("I am your rocking Servlet");&lt;/pre&gt;That's bad. It might be fine for exception messages (it's actually better to hard code exception messages in English). However, if you're writing a web front, you really need decent i18n. To those that have realized the importance of i18n, you would know that Java accomplishes this using properties files -- a huge set of key-value files where each localized message can be referred to using a unique key.&lt;br /&gt;&lt;br /&gt;An i18n properties file looks like this:&lt;br /&gt;&lt;pre&gt;# Snip&lt;br /&gt;property1=Welcome to this web site&lt;br /&gt;property2=I am your rocking Servlet&lt;br /&gt;# Snip&lt;/pre&gt;A i18n aware application would load i18n files like the one above and "substitute" the text to be shown to the user with the key to the intended localized message. For instance, instead of doing the below,&lt;br /&gt;&lt;pre&gt;httpServletResponse.getWriter().write("I am your rocking Servlet");&lt;/pre&gt;you would refer to the same message using its key, "property2".&lt;br /&gt;&lt;pre&gt;httpServletResponse.getWriter().write(myResourceBundle.getString("property2"));&lt;/pre&gt;So, if your application needs to be translated to Japanese, all you need to do is to &lt;a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html"&gt;create another properties file with the same common bundle name but with the Japanese locale appended to it&lt;/a&gt; and translate the values.&lt;br /&gt;&lt;br /&gt;So you'll probably end up with an i18n properties file that looks like this:&lt;br /&gt;&lt;pre&gt;# Snip&lt;br /&gt;property1=このウェブサイトへの歓迎&lt;br /&gt;property2=私はあなたの動揺のServletである&lt;br /&gt;# Snip&lt;/pre&gt;In most cases, the messages will actually display properly. However, the above is &lt;em&gt;bad&lt;/em&gt;. Why?&lt;br /&gt;&lt;br /&gt;The framework that you are using to build the web application probably is assuming that the i18n file is encoded with a specific character set. If the assumption is correct, you get your messages. If it is not, you will be scratching your head figuring out why your text appear to be gibberish. At that point, you probably know you have some sort of an encoding problem. I wish you all the best in fixing that if that happens to you -- debugging encoding problems is anything &lt;em&gt;but easy&lt;/em&gt;.&lt;br /&gt;&lt;br /&gt;Sure, maybe the framework you're using allows you to specify the character set to use when interpreting the i18n file, but then again, will you be certain enough to do so? What if someone rewrites the file in a different encoding?&lt;br /&gt;&lt;br /&gt;There are just too many ways to fuck yourself when assumptions are involved, particularly in the context of encoding.&lt;br /&gt;&lt;br /&gt;So, what's the "good" way? The answer is unicode escapes. You should translate your strings to "ascii-safe" characters that represent the Japanese text. If we take the example above, the properties file will look like this:&lt;br /&gt;&lt;pre&gt;# Snip&lt;br /&gt;property1=\u3053\u306e\u30a6\u30a7\u30d6\u30b5\u30a4\u30c8\u3078\u306e\u6b53\u8fce&lt;br /&gt;property2=\u79c1\u306f\u3042\u306a\u305f\u306e\u52d5\u63fa\u306eServlet\u3067\u3042\u308b&lt;br /&gt;# Snip&lt;/pre&gt;Localized i18n properties files that are constructed this way will be correctly interpreted by most (if not, all) character sets. However, how does one convert a string to unicode escapes? How about converting unicode escapes back to "normal" text for editing?&lt;br /&gt;&lt;br /&gt;The answer is... &lt;code&gt;native2ascii&lt;/code&gt; - A tool provided with the JDK specifically for those purposes.&lt;br /&gt;&lt;br /&gt;To convert text files to unicode escapes, you just need to issue the following command.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;native2ascii raw_japanese_bundle.properties myresourcebundle_jp.properties&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;To convert the escaped i18n properties file back to its raw form, just specify &lt;code&gt;-reverse&lt;/code&gt; to &lt;code&gt;native2ascii&lt;/code&gt;. For instance:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;native2ascii -reverse myresourcebundle_jp.properties raw_japanese_bundle.properties&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;And that's it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-2764861176521147504?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/2764861176521147504/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=2764861176521147504' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/2764861176521147504'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/2764861176521147504'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2008/07/drowing-in-worse-than-failures-this.html' title='Drowning in worse-than-failures. This time, i18n.'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-7118242988174922377</id><published>2008-07-30T15:00:00.009+08:00</published><updated>2008-08-05T23:35:40.111+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='automated testing'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='best practices'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='design patterns'/><title type='text'>Mad streak of worse-than-failures. This round, the singleton.</title><content type='html'>Singletons by themselves are evil, but are &lt;em&gt;un-smart&lt;/em&gt; when with Spring. They make things very untestable. Code like this makes me cringe in disgust &lt;em&gt;most&lt;/em&gt; of the time.&lt;br /&gt;&lt;pre&gt;private static final MySingleton SINGLETON = new MySingleton();&lt;br /&gt;&lt;br /&gt;public static MySingleton getInstance()&lt;br /&gt;{&lt;br /&gt;return SINGLETON;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private MySingleton()&lt;br /&gt;{&lt;br /&gt;/* Init stuff */&lt;br /&gt;readValueFromConfiguredDatabase();&lt;br /&gt;}&lt;/pre&gt;Assuming that &lt;code&gt;readConfigurationFromPropertiesFile()&lt;/code&gt; makes a connection to a database somewhere, what problem(s) may arise (in unit testing) from that?&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Your test will fail when MySingleton's class is initialized. If I recall correctly, you can't even get past the setup stage in your test cases.&lt;/li&gt;&lt;/ol&gt;Realizing the problem, some might change the code to:&lt;br /&gt;&lt;pre&gt;private static MySingleton SINGLETON;&lt;br /&gt;&lt;br /&gt;public synchronized static MySingleton getInstance()&lt;br /&gt;{&lt;br /&gt;if (null == SINGLETON)&lt;br /&gt;SINGLETON = new MySingleton();&lt;br /&gt;return SINGLETON;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private MySingleton()&lt;br /&gt;{&lt;br /&gt;/* Init stuff */&lt;br /&gt;readValueFromConfiguredDatabase();&lt;br /&gt;}&lt;/pre&gt;The above is bad because:&lt;br /&gt;&lt;ol&gt;&lt;li&gt; &lt;code&gt;getInstance()&lt;/code&gt; is synchronized. This might become a bottle in your application.&lt;/li&gt;&lt;li&gt; If &lt;code&gt;readValueFromConfiguredDatabase()&lt;/code&gt; throws some exception in the test because you don't have all the application configuration files in the test class path, you're screwed. Overriding it doesn't help because the &lt;code&gt;getInstance()&lt;/code&gt; method still instantiates &lt;code&gt;MySingleton&lt;/code&gt; and not a subclass of it.&lt;/li&gt;&lt;/ol&gt;Then, you might change the code to:&lt;br /&gt;&lt;pre&gt;protected static boolean TEST_MODE = false;&lt;br /&gt;&lt;br /&gt;private static MySingleton SINGLETON;&lt;br /&gt;&lt;br /&gt;public synchronized static MySingleton getInstance()&lt;br /&gt;{&lt;br /&gt;if (null == SINGLETON)&lt;br /&gt;SINGLETON = new MySingleton();&lt;br /&gt;return SINGLETON;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private MySingleton()&lt;br /&gt;{&lt;br /&gt;/* Init stuff */&lt;br /&gt;if (!TEST_MODE) readValueFromConfiguredDatabase();&lt;br /&gt;}&lt;/pre&gt;Which might work but is just as bad. Other than being butt ugly, it pollutes the meaning of the code.&lt;br /&gt;&lt;br /&gt;The good thing about Spring is that &lt;a href="http://static.springframework.org/spring/docs/1.2.x/reference/beans.html#beans-factory-modes"&gt;it takes care of the object instantiation stuff for you&lt;/a&gt;. You implement the beans, you tell Spring how to instantiate them and that's it. Spring makes your code simpler.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-7118242988174922377?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/7118242988174922377/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=7118242988174922377' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7118242988174922377'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7118242988174922377'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2008/07/mad-streak-of-worse-than-failures-this.html' title='Mad streak of worse-than-failures. This round, the singleton.'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-5842978106164841803</id><published>2008-07-29T20:46:00.003+08:00</published><updated>2008-07-29T20:56:38.666+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='best practices'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><title type='text'>Why regex is one of the worst ways to read HTML</title><content type='html'>How often do we find ourselves (or &lt;em&gt;others&lt;/em&gt;) reading data in HTML using a regex? Have you ever been reading paragraph (&lt;tt&gt;&amp;lt;p&amp;gt;&lt;/tt&gt;) text with regexes? For instance, you might start of with the regex below to capture all text between a &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&amp;lt;p.*?&amp;gt;(.*?)&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then you realize that there might be line breaks. So,&lt;br /&gt;&lt;br /&gt;&lt;code&gt;(?s)&amp;lt;p.*?&amp;gt;(.*?)&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then you realize that &amp;lt;p&amp;gt; tags could be a mix of cases. So,&lt;br /&gt;&lt;br /&gt;&lt;code&gt;(?si)&amp;lt;p.*?&amp;gt;(.*?)&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then you realize that there might be other tags between &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;... After a couple of iterations at fixing the problem, the regex has become so unbelievably complex, you don't know how you reached to create such a monster in the first place.&lt;br /&gt;&lt;br /&gt;Still, you persist.&lt;br /&gt;&lt;br /&gt;Then you realize there could be:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&amp;lt;!-- &lt;br /&gt;&amp;lt;p&amp;gt;Not needed anymore&amp;lt;/p&amp;gt; &lt;br /&gt;--&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;and that could be in JavaScript too.&lt;br /&gt;&lt;br /&gt;Then you realize it is wrong.&lt;br /&gt;&lt;br /&gt;The bottomline is this. &lt;em&gt;Never&lt;/em&gt; use regex to read structured (and usually complex) data. It might be a quick way around some problems, but it will come back to haunt you. If you do that, you can count on that.&lt;br /&gt;&lt;br /&gt;A more proper way would be to parse the HTML into a DOM. Then, using XML based technologies (XPath?), read the data in a structural way.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-5842978106164841803?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/5842978106164841803/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=5842978106164841803' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/5842978106164841803'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/5842978106164841803'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2008/07/how-often-do-we-find-ourselves-or.html' title='Why regex is one of the worst ways to read HTML'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-1981027687685754840</id><published>2008-06-21T15:28:00.005+08:00</published><updated>2008-07-26T18:05:09.133+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='xss'/><category scheme='http://www.blogger.com/atom/ns#' term='security'/><title type='text'>Yet another example of poor security in one of TMNet's many poorly written applications</title><content type='html'>It took me 5 minutes to craft this "attack". Don't believe me? Click the link below:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://tinyurl.com/6ayqv2"&gt;http://tinyurl.com/6ayqv2&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;And you'll get:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_3wFdvCO12KA/SFyuH3mMBLI/AAAAAAAAAA8/gD-KEfidP5U/s1600-h/another-xss.png"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://2.bp.blogspot.com/_3wFdvCO12KA/SFyuH3mMBLI/AAAAAAAAAA8/gD-KEfidP5U/s320/another-xss.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5214233918794630322" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-1981027687685754840?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/1981027687685754840/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=1981027687685754840' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/1981027687685754840'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/1981027687685754840'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2008/06/yet-another-example-of-poor-security-in.html' title='Yet another example of poor security in one of TMNet&apos;s many poorly written applications'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_3wFdvCO12KA/SFyuH3mMBLI/AAAAAAAAAA8/gD-KEfidP5U/s72-c/another-xss.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-6314824588889388072</id><published>2008-05-11T14:23:00.005+08:00</published><updated>2008-07-26T18:05:53.901+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='user interfaces'/><category scheme='http://www.blogger.com/atom/ns#' term='usability'/><category scheme='http://www.blogger.com/atom/ns#' term='humor'/><title type='text'>User Friendliness At Its Best</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_3wFdvCO12KA/SCaRBeo26SI/AAAAAAAAAA0/TR730aAnXPo/s1600-h/ErrorPrinting.jpg"&gt;&lt;img style="cursor: pointer;" src="http://4.bp.blogspot.com/_3wFdvCO12KA/SCaRBeo26SI/AAAAAAAAAA0/TR730aAnXPo/s320/ErrorPrinting.jpg" alt="" id="BLOGGER_PHOTO_ID_5199002274436147490" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;See rule #5 of &lt;a href="http://msdn.microsoft.com/en-us/library/aa511327.aspx"&gt;Top Rules for the Windows Vista User Experience&lt;/a&gt;&lt;br /&gt;&lt;a&gt;&lt;br /&gt;&lt;small&gt;The screen shot was shameless ripped from &lt;a href="http://ek-about.blogspot.com/2008/05/about-printer-connection-error-message.html"&gt;Eddie Kua's blog&lt;/a&gt;&lt;/small&gt;&lt;br /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-6314824588889388072?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/6314824588889388072/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=6314824588889388072' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/6314824588889388072'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/6314824588889388072'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2008/05/user-friendliness-at-its-best.html' title='User Friendliness At Its Best'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_3wFdvCO12KA/SCaRBeo26SI/AAAAAAAAAA0/TR730aAnXPo/s72-c/ErrorPrinting.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-3798493489594107370</id><published>2008-04-17T21:41:00.002+08:00</published><updated>2008-07-26T18:06:10.601+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='humor'/><title type='text'>The Real North Atlantic Treaty</title><content type='html'>&lt;img src="http://farm3.static.flickr.com/2092/2419399173_128324fd30.jpg?v=0" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Picture taken from &lt;a href="http://www.flickr.com/photos/terencey/sets/"&gt;Terence Yung's photostream&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-3798493489594107370?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/3798493489594107370/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=3798493489594107370' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/3798493489594107370'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/3798493489594107370'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2008/04/real-north-atlantic-treaty.html' title='The Real North Atlantic Treaty'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-7689768709339657473</id><published>2008-03-03T21:33:00.006+08:00</published><updated>2008-07-26T18:06:35.299+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='isolated classloading'/><title type='text'>The Pain Of Writing An Isolated ClassLoader</title><content type='html'>I've recently attempted something which I have been procrastinating for years --- to implement a Java isolated ClassLoader which has the ability to load classes from a JAR and the JARs in it.&lt;br /&gt;&lt;br /&gt;Specifically, the ClassLoader must be able to:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Load classes from a JAR file&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Load classes from JAR files inside the archive at a predefined location (inside of it). This has to be done recursively.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The source of the classes can be from anywhere which a &lt;code&gt;java.net.URL&lt;/code&gt; can point to.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Load different versions of classes which have the same fully qualified name. If the containing application has already loaded &lt;code&gt;x.y.Bar&lt;/code&gt;, the ClassLoader must be able to load a different version of it (if the zip file contains one). Think of it as a mini web application ClassLoader.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;So, where did I start? Extending the ClassLoader, of course. I knew I had to override some methods of &lt;code&gt;java.lang.ClassLoader&lt;/code&gt;. Obviously, my options were:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;code&gt;findClass(String):Class&lt;/code&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;code&gt;loadClass(String):Class&lt;/code&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;I mistakenly started off by overriding &lt;code&gt;findClass()&lt;/code&gt; (which turned out to be a horrible mistake. More on that later). To load bytes from a range of locations, I needed a URL. So, the ClassLoader contructor will take a URL as an argument. My idea was to call its &lt;code&gt;openStream&lt;/code&gt; method to read the content of the resource at the URL, which &lt;strong&gt;must&lt;/strong&gt; be a JAR/ZIP file.&lt;br /&gt;&lt;br /&gt;At that point, I asked myself how would I read JAR files in JAR files. I could write the entire resource to the local file system first before reading the individual entries. However, the mess of expanding a bunch of zip files to the local file system didn't appeal to me. I wanted to open the resource &lt;em&gt;on demand&lt;/em&gt; --- to open and read only what is necessary, and when it is necessary. After an hour, I finally settled on this approach.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;protected byte[] getClassDefinition(&lt;br /&gt;   final String fqcn,&lt;br /&gt;   final ZipInputStream zipInputStream) throws IOException {&lt;br /&gt; byte[] classDefintion = null;&lt;br /&gt; ZipEntry zipEntry;&lt;br /&gt;&lt;br /&gt; while (zipInputStreamHasMoreEntries)&lt;br /&gt; {&lt;br /&gt;   if (zipEntryIsJarFileAtThePredefinedLocation)&lt;br /&gt;   {&lt;br /&gt;     classDefintion = getClassDefinition(&lt;br /&gt;       fqcn,&lt;br /&gt;       new ZipInputStream(zipInputStream));&lt;br /&gt;   }&lt;br /&gt;   else&lt;br /&gt;   {&lt;br /&gt;     if (zipEntryMatchesTheClassWeAreLookingFor)&lt;br /&gt;     {&lt;br /&gt;       classDefinition = readContentsOfZipEntry();&lt;br /&gt;       break;&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; return classDefinition;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The code basically says:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;While we have not found the zip entry we are looking for or went through all zip entries, do:&lt;br /&gt; &lt;ul&gt;&lt;br /&gt;   &lt;li&gt;If current entry is a zip/JAR file, open a ZipInputStream from the current ZipInputStream and perform a recursive search into it.&lt;/li&gt;&lt;br /&gt;   &lt;li&gt;Else, check if the entry is what we are looking for.&lt;br /&gt;     &lt;ul&gt;&lt;br /&gt;       &lt;li&gt;If the entry matches what we are looking for, load it and break from the loop.&lt;/li&gt;        &lt;/ul&gt;      &lt;/li&gt;    &lt;/ul&gt;  &lt;/li&gt;&lt;br /&gt;&lt;li&gt;Return the loaded class, if any.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;On a side note: if there is a better way than recursion, I really would love to hear about it.&lt;br /&gt;&lt;br /&gt;That took care of loading classes from a JAR file or the JAR files in it... and that, from any location which a URL can point to. However, at that point, I wasn't sure if it would load different versions of classes which were already loaded. I need to test it, but how? How could I express the validation as a JUnit test case? Fortunately, my memory was my friend (and still is). I remembered this rule.&lt;br /&gt;&lt;br /&gt;&lt;em&gt;Two classes of the same fully qualified name are not equal to each other if they are loaded by different ClassLoaders. They cannot be cast to each other and such operations would result in a ClassCastException&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;Normally, I feared the above scenario (especially when Commons Logging is one of the libraries used in the concerned application). However, at that time, I needed to simulate that. Oh the irony.&lt;br /&gt;&lt;br /&gt;To implement the test scenario, obviously, I needed a zip file which contains classes which will already be loaded during the test's execution. Out of pure randomness, I chose StringUtils from the Commons Lang library. The test class is already using that class to do a variety of things. StringUtils would be one of the most obvious choices to test the ClassLoader with.&lt;br /&gt;&lt;br /&gt;This is how my test case look like.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public void testLoadClassesFromZipFileInZipFile() throws Exception {&lt;br /&gt; final URL dummyZipFile = getClass().getClassLoader().getResource(&lt;br /&gt;     "com/foo/bar/test.jar");&lt;br /&gt; final Class stringUtilsClass;&lt;br /&gt; final StringUtils stringUtils;&lt;br /&gt;&lt;br /&gt; myClassLoader = new MyClassLoader(dummyZipFile);&lt;br /&gt; stringUtilsClass = myClassLoader.loadClass(&lt;br /&gt;     "org.apache.commons.lang.StringUtils");&lt;br /&gt;&lt;br /&gt; assertFalse(stringUtilsClass.equals(StringUtils.class));&lt;br /&gt;&lt;br /&gt; try {&lt;br /&gt;   stringUtils = (StringUtils) stringUtilsClass.newInstance();&lt;br /&gt;   fail("ClassCastException should be raised");&lt;br /&gt; } catch (final ClassCastException cce) {&lt;br /&gt;   /*&lt;br /&gt;    * Success - two classes with the same fqcn loaded by two&lt;br /&gt;    * different classloaders can't be cast into the other&lt;br /&gt;    */&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;... and of course, the test failed miserably. The expression, &lt;code&gt;stringUtilsClass.equals(StringUtils.class)&lt;/code&gt; returned &lt;code&gt;true&lt;/code&gt;. Wtf?!? After countless head banging against my indestructable CRT monitor, I tried overriding &lt;code&gt;ClassLoader.loadClass(String):Class&lt;/code&gt;. I've overriden it to call &lt;code&gt;getClassDefinition()&lt;/code&gt; (the method I have defined earlier) and use its return value as one of the arguments to the &lt;code&gt;ClassLoader.defineClass()&lt;/code&gt; method. With my fingers crossed, I executed the test again.&lt;br /&gt;&lt;br /&gt;The test failed miserable, again. It is now failing with a &lt;code&gt;NoClassDefFoundError&lt;/code&gt; related &lt;code&gt;java.lang.Object&lt;/code&gt;. WTF?! Looking through my code again, I was very sure that I called &lt;code&gt;super.loadClass&lt;/code&gt; if the ClassLoader couldn't load it. What could be wrong?&lt;br /&gt;&lt;br /&gt;Unsurprisingly, turning to my monitor with violence solved the problem. It appeared that I need to pass the call to a parent ClassLoader, which is not done by the super class's version of &lt;code&gt;loadClass&lt;/code&gt;. After refactoring the ClassLoader a bit, the &lt;code&gt;loadClass&lt;/code&gt; method then calls the &lt;code&gt;loadClass&lt;/code&gt; method of the set parent ClassLoader as required.&lt;br /&gt;&lt;br /&gt;With a bit of fear and anxienty, I executed the test again.&lt;br /&gt;&lt;br /&gt;All green! Whew!&lt;br /&gt;&lt;br /&gt;With that, I wrote even more test scenarios. They include:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Test if ClassNotFoundException would be raised if the requested class could not be found.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Test if ClassCastException would be raised if the zip file does not contain the class, but the parent ClassLoader does.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;To my relieve, all were green! My heart rate started to drop. I could no longer feel blood rushing to my head. The ClassLoader works!&lt;br /&gt;&lt;br /&gt;Looking at my code, I started to review the mistakes which were made... and the hours which I have spent could be summed up by the items below.&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;If you want to load different versions of classes which are already loaded, you &lt;strong&gt;must&lt;/strong&gt; override &lt;code&gt;loadClass&lt;/code&gt;.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;If you are overriding &lt;code&gt;loadClass&lt;/code&gt; make sure you call the same method of a parent ClassLoader if you can't load it at that level.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;If you just want to add more classes (no loading of different versions of a class), overriding &lt;code&gt;findClass&lt;/code&gt; is enough.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;RTFM, carefully that&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;I have done similar things for classpath resources. More on that in my next blog.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-7689768709339657473?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/7689768709339657473/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=7689768709339657473' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7689768709339657473'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7689768709339657473'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2008/03/perils-of-writing-isolated-classloader.html' title='The Pain Of Writing An Isolated ClassLoader'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-7151209797970844250</id><published>2008-02-11T20:41:00.002+08:00</published><updated>2008-07-26T18:08:15.063+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='humor'/><category scheme='http://www.blogger.com/atom/ns#' term='wtf'/><category scheme='http://www.blogger.com/atom/ns#' term='shame'/><title type='text'>Can You Spot The Mistakes?</title><content type='html'>Some Java nerdery here:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:90%;"&gt;public class Anonymized&lt;br /&gt;{&lt;br /&gt;  private static Logger logger = Logger.getLogger(Anonymized.class.getName());&lt;br /&gt;&lt;br /&gt;  private static String sql = null;&lt;br /&gt;  private static PreparedStatement stmt = null;&lt;br /&gt;  private static ResultSet rs = null;&lt;br /&gt;&lt;br /&gt;  public static void  getAnonymizedDetail(Connection conn, AnonymizedRequestObject requestObj)&lt;br /&gt;      throws Exception&lt;br /&gt;  {&lt;br /&gt;    sql = "UPDATE anonymized_table" +&lt;br /&gt;        " SET anonymized_field_1 = ?" +&lt;br /&gt;        " anonymized_field_2 = ?" +&lt;br /&gt;        " anonymized_field_3 = ?" +&lt;br /&gt;        " anonymized_field_4 = ?" +&lt;br /&gt;        " anonymized_field_5 = ?" +&lt;br /&gt;        " anonymized_field_6 = ?" +&lt;br /&gt;        " anonymized_field_7 = ?" +&lt;br /&gt;        " anonymized_field_8 = ?" +&lt;br /&gt;        " anonymized_field_9 = ?" +&lt;br /&gt;        " anonymized_field_10 = ?" +&lt;br /&gt;        " anonymized_field_11 = ?" +&lt;br /&gt;        " WHERE anonymized_field_12 = ?";&lt;br /&gt;&lt;br /&gt;    stmt = conn.prepareStatement(sql);&lt;br /&gt;    /* Snip */&lt;br /&gt;    &lt;br /&gt;    if (aNormalConditionCheck) /* Edited to improve clarity */&lt;br /&gt;      stmt.setString(9, "F");&lt;br /&gt;    else&lt;br /&gt;      stmt.setString(9, "");&lt;br /&gt;&lt;br /&gt;    /* Snip */&lt;br /&gt;  &lt;br /&gt;    logger.debug("Update anonymized_table with primary key anonymized_field_12 = " + requestObj.getPrimaryKey());&lt;br /&gt;  &lt;br /&gt;    if (requestObj.getPrimaryKey()!=0)&lt;br /&gt;    {&lt;br /&gt;      stmt.setLong(12, requestObj.getPrimaryKey());&lt;br /&gt;      rs = stmt.executeQuery();&lt;br /&gt;    }&lt;br /&gt;    else&lt;br /&gt;    { &lt;br /&gt;      throw new Exception("Cannot save result to DB, because anonymized_field_12 is missing in AnonymizedRequestObject request object!");&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;How many mistakes (minus coding style errors and the nonsensical anonymized names ) are there in the above code? The one with correct answer gets a cookie (I'll mail it to you).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-7151209797970844250?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/7151209797970844250/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=7151209797970844250' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7151209797970844250'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7151209797970844250'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2008/02/can-you-spot-mistakes.html' title='Can You Spot The Mistakes?'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-7520279792066233350</id><published>2008-01-12T14:04:00.001+08:00</published><updated>2008-07-26T18:06:55.295+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='methodology'/><title type='text'>Rethinking Software Development</title><content type='html'>I guess it's fair to say that most of us who are in the software delivery field are familiar with the &lt;i&gt;implement-&gt;build-&gt;review&lt;/i&gt; cycle in software development. Regardless of the development methodology practiced, those phases will be there, with similar names or not. To reiterate, typical software development usually goes like this.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;Implement&lt;/b&gt; - This is where you get a few developers to implement the functional requirements of an application. Additional activities which might happen in this phase are (re)engineering, sub system deprecations and so forth.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;Build&lt;/b&gt; - Once the functional requirements are implemented, you build your application. The built application could then be used in various testing activities.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;Review&lt;/b&gt; - This is judgment day for the built application. Implemented functionalities are validated to ensure they meet the requirements' specifications. Existing functionalities are checked to ensure there is no breakage. This is also the point where the stakeholders of the application decide whether to pass the current development iteration or to steer it into another one, possibly with new specifications and directions.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;Pretty standard and complete? Yes, I would agree. However, if you look at it from another dimension, a few areas could really use some improvements. At least, that's true from my experience.&lt;br /&gt;&lt;br /&gt;Most of the timelines I was &lt;i&gt;fortunate&lt;/i&gt; enough to see have a similar pattern.&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Time available for development is short.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Time allocated for each build usually has a duration of 1-2 man days.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Time allocated for testing is &lt;b&gt;very&lt;/b&gt; significant.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Immediately, a few problems stand out:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Your developers will be racing to a close-to impossible due date. Just to ensure the implementation is delivered on time, the importance of proper design and impact weighing are reduced. That has serious consequences. There is a good chance your testers will be crushed by testing work. There will be significantly more defects with the application.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Even a day of time allocated for application build is a waste. Building an application should not be difficult at all. The complexity of it doesn't justify for the time allocated. While I have been in situations where building the application (in effect, is really integrating the various components in the system) revealed unimaginable amounts of problems, that happened because we did not have a good enough infrastructure. More on that later.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;While it is undeniable that testing is the most crucial part of application development, that must not be represented by a huge chunk of time allocated for it. Take a moment to think. Do you foresee that many problems with the application that you need to have this gigantic testing phase? If you do, don't you think that might be a precursor of something bigger that is wrong?&lt;br /&gt;&lt;br /&gt;The testing and implementation phases have direct effects on each other. The less time you have for implementation, the more time you need to validate the quality of its outcome.  If you follow that and go through each development cycle, there will always be more issues passed to next development cycle for fixing. More time will need to be used to validate the fixes... and then it goes on. The cycle is almost vicious.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;At this point, you may ask, what's the revolutionary idea to solve all the problems? Well, there is none. The idea I will be proposing soon is proven. There is nothing revolutionary about it. That's what made me so compelled to share.&lt;br /&gt;&lt;br /&gt;I still remember a joke which a friend of mine made about how a country got things done. He said, the idea of the country is.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;If you can't do it with a machine, do it with 1000 men.&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;But what I want to say is that if you need 1000 men to do something, there is a good chance it can be done with a machine. What I'm about to propose will require a lot of changes in the development practice which I have described earlier. The time allocated for the phases in it will be completely different. How different?&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Instead of allocating the least time for development, have it to take up the largest amount of time allocated for the application delivery.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Take some of the time allocated for the testing phase and give it to the implementation phase.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Eliminate the time allocated for application build by introducing automated &lt;a href="http://en.wikipedia.org/wiki/Continuous_integration"&gt;continuous integration&lt;/a&gt;. At least, make it really insignificant.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Automate most of the testing.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;That model gives us the following benefits.&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;With more time available for implementation, &lt;b&gt;your developers will be able to take the best implementation path&lt;/b&gt; by carefully reviewing designs and analyzing impact. This should allow them to produce better quality work, which will lessen to load on the coming phases.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;Your developers, with the time given to them, will be able to write test cases, both unit and functional&lt;/b&gt;. These tests I'm talking about are automated tests. Tests which are repeatable anytime to validate the application. There are plenty of technologies which the developer can use to automate the testing. I have a preference for &lt;a href="http://www.openqa.org/selenium/"&gt;Selenium&lt;/a&gt; for functional testing. It is an excellent tool to test web applications using browsers as the test agents. It is very close to human testing and it can be completed with a fraction of the time taken by a human tester.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Since your developers are required to write test cases, &lt;b&gt;application defects will be realized earlier - even before they reach the build phase!&lt;/b&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Allows for shorter but more frequent development cycles. Implementation is an overhead. Once the code base is ready, changing it requires dramatically less effort. With the time taken to test the application a constant (or close to), the next development cycle will almost always be shorter than the previous one. More development cycles translate to better application quality and that, in turn, translates to a happy customer in the end - what really matters.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;What's your take?&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-7520279792066233350?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/7520279792066233350/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=7520279792066233350' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7520279792066233350'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7520279792066233350'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2008/01/rethinking-software-development.html' title='Rethinking Software Development'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-5792415073977994137</id><published>2007-10-16T22:59:00.002+08:00</published><updated>2008-07-26T18:07:12.818+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='design patterns'/><category scheme='http://www.blogger.com/atom/ns#' term='architecture'/><title type='text'>Interface &amp; Abstract Classes</title><content type='html'>Oh troll! Oh yes. That's right.&lt;br /&gt;&lt;br /&gt;This is another take at this old topic. Only my perceptions differ. I'll be looking at this topic from an architectural point of view. So... when to interface and when to not? Before we head to that, let's take a look at interfaces.&lt;br /&gt;&lt;br /&gt;Interfaces are skeletons. They do not contain behavior. They are perfect as specifications. Remember that specifications are really &lt;b&gt;just&lt;/b&gt; standards. You can choose to implement those specifications however you wish and it wouldn't matter how you implement them. They are right as long as they conform to the specifications. That's how Java interfaces work. They define standards of services and service providers implement them. This makes sense. All service providers implement their services in very distinctive ways. If the specifications define implementation methods, they would most probably be ignored anyways...&lt;br /&gt;&lt;br /&gt;Java interfaces also make it easier to implement applications which are service oriented. Take SOAP services for example. You don't need to know how they are implemented. You just need to know their inputs and outputs (specifications).&lt;br /&gt;&lt;br /&gt;Java interfaces help you to decouple your service specifications from implementation. If it were abstract classes which defined service specifications, then any implementation other than the default will have to &lt;b&gt;extend&lt;/b&gt; the default implementation. That's clunky, ugly and totally illogical. To make it simple, it doesn't make sense for the next different ruling party of your country to inherit all the corruption of the current ruling party to provide you with the same administrative service.&lt;br /&gt;&lt;br /&gt;Generally, it is safe to use Java interface wherever you can observe specifications. Typical areas would be managers, DAOs and loggers. &lt;br /&gt;&lt;br /&gt;Unit tests could actually benefit from interfaces as well. Mock object frameworks usually support proxying of Java interfaces at the very least. That will allow you to create and verify mocks easily.&lt;br /&gt;&lt;br /&gt;Abstract classes do have their use, of course. In fact, they are equally as priceless. For instance, have you ever find yourself in a situation where you need to have a common sequence of method invocations of two related classes? That's the Template design pattern. That's one example abstract classes is perfect for. Interfaces just wouldn't fit in that scenario.&lt;br /&gt;&lt;br /&gt;It doesn't hurt to have interfaces. In fact, it doesn't hurt if you have a lot of them. Of course, there is this thin fine line. If you decide to make your DTOs interfaces, don't define setters. I don't think they make sense of an interface.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-5792415073977994137?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/5792415073977994137/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=5792415073977994137' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/5792415073977994137'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/5792415073977994137'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2007/10/interface-abstract-classes.html' title='Interface &amp; Abstract Classes'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-7835487418579723844</id><published>2007-10-10T00:24:00.001+08:00</published><updated>2008-07-26T18:07:33.603+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='process'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='methodology'/><category scheme='http://www.blogger.com/atom/ns#' term='infrastructure'/><title type='text'>How I Would Start Development</title><content type='html'>Alright. So you've battled through the user requirements, figured out the time needed for engineering tasks according to their difficulties, hammered out the timelines with the project managers and you finally have that nice little document called the functional requirement specifications signed off. It's now when the most painful part of the software development cycle starts. Yes, the development.&lt;br /&gt;&lt;br /&gt;I'm not going to talk about software development methodologies. If I wanted to, I wouldn't have started this blog after the functional requirements are frozen. Development methodologies, much like religions, should be decided way before that.&lt;br /&gt;&lt;br /&gt;To fend off unintended readers, this blog will not be talking about the foot-stepping dances of development. This blog post, to be specific, will talk about what are the things which have to be done/acquired to get development rolling.&lt;br /&gt;&lt;br /&gt;The way I see it, there are four main components. They are:&lt;br /&gt;&lt;br /&gt;* &lt;a href="http://en.wikipedia.org/wiki/Source_Code_Management"&gt;Source control management&lt;/a&gt;&lt;br /&gt;* &lt;a href="http://martinfowler.com/articles/originalContinuousIntegration.html"&gt;Continuous integration&lt;/a&gt;&lt;br /&gt;* &lt;a href="http://en.wikipedia.org/wiki/Issue_tracking"&gt;Issue tracking&lt;/a&gt;&lt;br /&gt;* &lt;a href="http://en.wikipedia.org/wiki/Knowledge_management"&gt;Knowledge management&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Source control management&lt;/b&gt;&lt;br /&gt;For most development projects, there aren't many to choose from. The choices are:&lt;br /&gt;&lt;br /&gt;* &lt;a href="http://en.wikipedia.org/wiki/Subversion_%28software%29"&gt;SVN (Subversion)&lt;/a&gt;&lt;br /&gt;* &lt;a href="http://en.wikipedia.org/wiki/Concurrent_Versions_System"&gt;CVS (Concurrent Versions System)&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The decision will ultimately be decided by a battle of convenience. Setting up both systems are equally as troublesome. Their setups both include the prepping of certain directories which will eventually become the source repositories (and not failing to mention the massive clunk of permission problems following thereafter). However, for the trouble, SVN gains you one thing. The ability to view your sources over HTTP without setting up yet another subsystem. Also, HTTP(s) might be a better option if you work in restrictive environments. For instance, you might be working at your client's office where the only outgoing connections you can make are HTTP or SSL.&lt;br /&gt;&lt;br /&gt;SVN does lose out a bit without all that branching and tagging features. However, source directories can be copied along with their histories. That should make up a bit.&lt;br /&gt;&lt;br /&gt;Of course, the &lt;span style="font-weight:bold;"&gt;bad thing&lt;/span&gt; about SVN is that it is too easy to screw up your local copy of the source code. Suddenly, you will find yourself losing the ability to commit your code or change attributes of certain files. At that point, the only thing left to do is to check out the code your are working on and reapply the changes. That sucks.. but SVN still sucks lesser compared to CVS.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Continuous Integration&lt;/span&gt;&lt;br /&gt;While a lot of a developer's work is done alone, the compatibility of the works of each developer is important. Every now and then, you've got developers trying to wrestle each other out to win the right to put the blame to the other for breaking the build (yes, that shameful monkey). You need a way (or tool) which checks for integration breakage regularly and be able to notify the relevant people about those breakages as soon as possible.&lt;br /&gt;&lt;br /&gt;It would be best to implement some enabling technologies which do continuous integration. Builders like Maven definitely help in this area. However, if Maven is used alone in this context, that probably means you have a scheduled build.. and the interval would be fixed to a day (24 hours).&lt;br /&gt;&lt;br /&gt;That is too much lag. You don't want to wait a day to find out that someone else broke the code and you have to spend the entire morning fixing the problems when you could have yesterday, if you weren't chattering like a bird then. By the way, that's probably the case if you actually check in the morning if the build broke. That monkey who broke it probably has no idea.&lt;br /&gt;&lt;br /&gt;A continuous integration system will help in this context. It will constantly scan for changes in your source repositories and rebuild your applications if there are changes. This means breakages will be detected and fixed with much lesser delay. Of course, that can only happen if the system actually notifies the relevant people. With a continuous integration system, that's not something hard to do.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Issue Management&lt;/span&gt;&lt;br /&gt;Unless the application being developed is unbelievably simple, you will have issues. When issues arise, they have to be tracked in a way where:&lt;br /&gt;&lt;br /&gt;* People in different roles of your project can utilize the work flow of the system to systematically get things done. For example, a bug raised by a tester will be assigned to the developer. When the developer has fixed the bug, it will be assigned to the tester where he or she can check if there is a need to reopen the bug report.&lt;br /&gt;* You can easily see what issues are open, closed and in progress. This gives you an overall view of the state of the project. Very valuable information.&lt;br /&gt;* You can easily see where are the hot areas of the application. It allows you to make some changes to your project plans to help the hot areas.&lt;br /&gt;* You can easily see how much time is spent versus the time estimated for the issues.&lt;br /&gt;* You can extract certain statistics from the issues tracked to help drive the project.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Knowledge Management&lt;/span&gt;&lt;br /&gt;Knowledge will be gained by the people involved in the project as they work on it. Ideally, everyone should have the same knowledge of the project. That's great because someone in your project doesn't end up having to spend unneeded days (or months) to relearn something which someone else already have. That could happen as people come and go in a company. Of course, that might also happen if that someone with the exclusive knowledge is on a long holiday.&lt;br /&gt;&lt;br /&gt;To capture knowledge, you must of course, get your people to adopt the culture of documenting what they know. Constant nagging and rewards might help.&lt;br /&gt;&lt;br /&gt;Once you have that, you need a tool which allows your people create, edit and search knowledge bases easily. Capturing knowledge doesn't require complex content authoring features. In most cases which I know, systems which allow users to create documents with light formatting using some annotation is sufficient. For instance, wiki markup. WYSIWYG editors might be helpful too.&lt;br /&gt;&lt;br /&gt;However, the most important part would be the searching. The problem with 12 dozen MS Word documents in a Windows file share is that they are not searchable. Looking for knowledge immediately becomes a time consuming task. Knowledge searching should be fast and accurate.&lt;br /&gt;&lt;br /&gt;To me, once the four components are in place, development can start and one can definitely be assured that the application being developed meets a certain minimal level of quality. You can definitely place other forms of control or assertions. For instance, checkstyle, refactoring and documentation standards. However, those can't happen if the four components are not there.&lt;br /&gt;&lt;br /&gt;So how would you start your development?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-7835487418579723844?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/7835487418579723844/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=7835487418579723844' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7835487418579723844'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/7835487418579723844'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2007/10/how-i-would-start-development.html' title='How I Would Start Development'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-5153840671972869943</id><published>2007-09-05T20:01:00.001+08:00</published><updated>2008-07-26T18:07:54.168+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='automated testing'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><title type='text'>Test With All Your Might</title><content type='html'>I've been creating a lot of test cases these days. It's quite fun and challenging. As with my previous post, I had to understand what the code are trying to do before actually writing the test cases. I believe, in the ideal world, test cases are written first - &lt;a href="http://en.wikipedia.org/wiki/Test_driven_development"&gt;TDD (Test Driven Development)&lt;/a&gt;. The idea does make some sense.&lt;br /&gt;&lt;br /&gt;When you implement something, you (the developer) know what will (or supposed to) be the input and the output. That's specification. When you have specification (a formal definition of something), you can validate against it. That's when unit test cases come in. They validate the implementation of the specifications. However, the question is, how can you write unit tests when you don't know how the methods look like? The answer to that comes in the form of Java interfaces. Interfaces are really specifications of unit behavior. With support of refactoring tools, I guess it is not so hard to create well designed applications which are thoroughly tested.&lt;br /&gt;&lt;br /&gt;The only downside to that would be the increased development time. That's because developers have to write test cases &lt;span style="font-weight: bold;"&gt;before&lt;/span&gt; they can write actual code! In broader perspectives, that practice (TDD) might actually look like bad idea. However, if building products which have to be regressively tested to ensure quality through their lifetimes is important, the practice will save a lot of time in the long run.&lt;br /&gt;&lt;br /&gt;While writing test cases today, I came across two interesting things, which I will break them down into two sections to please the tired eyes of Confluence lurkers.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Comparator&lt;/span&gt;&lt;br /&gt;Java comparators compare two (left hand side, right hand side) values to determine which one is larger or if they are equal. According to the Java specification, implementations of Java comparators must:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Return a value lesser than 0 if the left hand side value is lesser than the right hand side value&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Return {{0}} if the left hand side value is equals to the right hand side value&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Return a value larger than 0 if the left hand side value is greater than the right hand side value&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;Of course, I say this with the awareness that the Comparator actually have to be consistent with equals. If given the left hand side value as &lt;span style="font-family: courier new;"&gt;lhs&lt;/span&gt; and right hand side value as &lt;span style="font-family: courier new;"&gt;rhs&lt;/span&gt;, &lt;span style="font-family: courier new;"&gt;lhs.equals(rhs)&lt;/span&gt; must produce the same result as &lt;span style="font-family: courier new;"&gt;comparator.compare(lhs, rhs) == 0&lt;/span&gt;. But today, I had a revelation:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;public class EditCountAuthorRankingComparator &lt;br /&gt;    implements Comparator {&lt;br /&gt;&lt;br /&gt;  public int compare(Object obj1, Object obj2) {&lt;br /&gt;    AuthorRanking ranking1 = (AuthorRanking)obj1;&lt;br /&gt;    AuthorRanking ranking2 = (AuthorRanking)obj2;&lt;br /&gt;&lt;br /&gt;    if (ranking1.getEdits() &lt; ranking2.getEdits()) {&lt;br /&gt;      return 1;&lt;br /&gt;    }&lt;br /&gt;  &lt;br /&gt;    if (ranking1.getEdits() &gt; ranking2.getEdits()) {&lt;br /&gt;      return -1;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    // finally order alphabetically&lt;br /&gt;    return ranking1.getFullNameString().compareToIgnoreCase(&lt;br /&gt;        ranking2.getFullNameString());&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;It would be fine if the whole class was renamed to something like ReverseEditCountAuthorRankingComparator, but then again, that would be slapping myself because of the last line in the above method. :)&lt;br /&gt;&lt;br /&gt;Of course, the code failed my unit test. For me, I don't really care if the code is working acceptance tests (manual, currently), as long as it doesn't follow unit specifications, it's a &lt;span style="color: rgb(255, 0, 0);"&gt;red&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Maven Surefire too eager&lt;/span&gt;&lt;br /&gt;While building one the plugins with tests, Maven complained and complained about this:&lt;br /&gt;&lt;pre&gt;org.apache.maven.surefire.testset.TestSetFailedException: &lt;br /&gt;Unable to instantiate POJO &lt;br /&gt;'class com.atlassian.confluence.extra.&lt;br /&gt;impresence2.TestGtalkPresenceReporter$10'&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;a href="http://jira.codehaus.org/browse/SUREFIRE-288"&gt;W00t&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;That's all folks.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-5153840671972869943?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/5153840671972869943/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=5153840671972869943' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/5153840671972869943'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/5153840671972869943'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2007/09/test-with-all-your-might.html' title='Test With All Your Might'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-2078616677134562383</id><published>2007-08-15T23:50:00.002+08:00</published><updated>2008-07-26T18:09:05.135+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='xss'/><category scheme='http://www.blogger.com/atom/ns#' term='security'/><title type='text'>Shut up and encode HTML</title><content type='html'>&lt;b&gt;No&lt;/b&gt;, I could not say it in a better way. We, web developers, have slapped ourselves with enough security holes which could be avoided by a little bit more effort in thinking before writing our code. If I haven't got your attention yet, I really mean - &lt;b&gt;HTML encode bloody variables&lt;/s&gt; when generating HTML.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;So what can happen if you do not encode your variables? Well, &lt;a target="_blank" href="http://tinyurl.com/2tap8c"&gt;this&lt;/a&gt; clearly describes it. That's &lt;a href="http://en.wikipedia.org/wiki/Xss"&gt;XSS&lt;/a&gt;. (for those who are wondering, the TMNet web site was the first choice in my list of possible web sites which are vulnerable to XSS. The corporation is high on the suckage factor).&lt;br /&gt;&lt;br /&gt;The funniest thing is how easily those security holes can be avoided. All it takes is to &lt;a href="http://commons.apache.org/lang/api-release/org/apache/commons/lang/StringEscapeUtils.html#escapeHtml(java.lang.String)"&gt;encode your string&lt;/a&gt; variables. In my experience, the only times when I saw HTML encoding of string variables were the times they were used as hidden input field values in a HTML form.. and unescaped strings just completely borked the sites' layouts. I guess now we don't have a reason to not encode HTML, do we?&lt;br /&gt;&lt;br /&gt;Unlike SQL injection, XSS attacks are easier to create because the attackers are not required to have knowledge of the database schema. All they need to know is JS.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-2078616677134562383?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/2078616677134562383/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=2078616677134562383' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/2078616677134562383'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/2078616677134562383'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2007/08/shut-up-and-encode-html.html' title='Shut up and encode HTML'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-8612094418008199720</id><published>2007-06-23T10:25:00.001+08:00</published><updated>2008-07-26T18:09:23.407+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random interesting stuff'/><title type='text'>Firstborn Get the Brains</title><content type='html'>For those who doesn't read &lt;a href="http://slashdot.org"&gt;/.&lt;/a&gt; often, here's a &lt;a href="http://science.slashdot.org/article.pl?sid=07/06/22/1233208&amp;from=rss"&gt;post&lt;/a&gt; which I think is quite interesting.&lt;br /&gt;&lt;br /&gt;And... here's the part where my two cents comes:&lt;br /&gt;&lt;br /&gt;I think the post is a wealthy load of unmistakable garbage. Assuming the statistics are correct, that firstborns are more intelligent than their siblings by a few points, using their social statuses in later stages of their lives as a benchmark relative to the time when their IQ is sampled just simply can't bang any sense into my reluctant mind.&lt;br /&gt;&lt;br /&gt;I hear the chants of why. Well, IQ change and how depends on the environment and exposure that a person is subjected to. Education, family, friends, experience, attitude are some of the factors which could have very visible effects on a person's IQ. Using birth order as a coefficient to IQ measurement just doesn't make sense. If that is at all slightly true, then the article must be written by the youngest child.&lt;br /&gt;&lt;br /&gt;Having said all that, the eldest child usually have traits that might not be evident in younger children which I believe could contribute to their success in securing a respectable rank in society.&lt;br /&gt;&lt;br /&gt;    * Independent - Eldest child are usually less dependent on people. Probably that's because they have not many people to depend on&lt;br /&gt;    * Leadership - Eldest child are usually asked to look after their younger brothers or sisters.&lt;br /&gt;&lt;br /&gt;There are perhaps some more traits. However, the caffeine which I consume hasn't yet trigger the gush of blood into my head. For motoring enthusiasts, that's the "turbo lag". The time lapsed between the pedal to the metal and extra power in acceleration. Anyways, coming back to the topic, there are also several factors which could help the eldest child to achieve success.&lt;br /&gt;&lt;br /&gt;Usually, more resources are available to facilitate the first child's growth. Education funds, food, and so forth. While from my observations most parents have some financial planning for their kids, the ratios at which resources are distributed are usually unbalanced. It's not a blame which I am describing about. It's perfectly understandable. A plan is an estimate not a specification.&lt;br /&gt;&lt;br /&gt;In all, what we do will shape us. You can crawl from the dumps but you can fly to reach the skies.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-8612094418008199720?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/8612094418008199720/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=8612094418008199720' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8612094418008199720'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8612094418008199720'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2007/06/firstborn-get-brains.html' title='Firstborn Get the Brains'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-4531829399657225801</id><published>2007-06-02T12:48:00.001+08:00</published><updated>2008-07-26T18:09:48.846+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='infrastructure'/><title type='text'>Technology, Technology O' Technology Tonight</title><content type='html'>I hate to use papers. I seriously do. I have to warn you that this is some sort of an extremist post, however, this is my blog and I write anything I wish to here, as long as &lt;a href="http://nationmultimedia.com/worldhotnews/read.php?newsid=30033518"&gt;my government says I can&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Papers are always not so useful to me. Why?&lt;br /&gt;&lt;br /&gt;* They are not indexed. I can't search for data contained in them.&lt;br /&gt;* They are subjected to wear and tear. Ink fade over time.&lt;br /&gt;* They are usually not protected against fires and other mishaps -- you do not have a redundant backup too.&lt;br /&gt;* Their data presentation does not have an adaptable interface -- just one way of representing the data. For example, if they are notes, it contains the information which you understand at that point it time. It becomes useless as your mindset mutates or time goes by.&lt;br /&gt;* There is a higher chance for me to lose the information than to use it.&lt;br /&gt;* They are costly.&lt;br /&gt;* They freaking cut my fingers.&lt;br /&gt;* I hate to shop for them.&lt;br /&gt;&lt;br /&gt;Personally, I try to move everything to electronic format. Why? It addresses all of the drawbacks above and more. Here are my sources of information.&lt;br /&gt;&lt;br /&gt;* Books - PDF documents&lt;br /&gt;* Mail - Email&lt;br /&gt;* News - RSS feeds&lt;br /&gt;* Knowledge base - My personal content management system (a wiki)&lt;br /&gt;* Notes - PDA&lt;br /&gt;* Contacts - My PDA, synchronized with an Exchange server&lt;br /&gt;* Notes - My PDA, synchronized with an Exchange server&lt;br /&gt;* Calendar - My PDA, synchronized with an Exchange server&lt;br /&gt;* Task tracker - My PDA and issue tracker web application&lt;br /&gt;&lt;br /&gt;There are additional stuff which I do online. Some of them are paying bills, banking and so forth. &lt;br /&gt;&lt;br /&gt;The other thing which I would love to see "computerized" is signatures. Currently, I still have to have an organic signature, something I had to do with a pen and thin piece of paper. I think it would be great if my bio-signature can be used to link to my OpenPGP profile.&lt;br /&gt;&lt;br /&gt;I guess, my enthusiasm for everything to be computerized is fueled by my desire to be accurate and effective. I don't want to lose any information, I want information to be captured and secured, and I want to be able to refine those information to something which I can use to improve myself. Currently, in our system, it is really hard to manage information, even if it is just our own. Ask yourself the questions below.&lt;br /&gt;&lt;br /&gt;* Can you track down your spendings down to the last cent accurately and then categorize them?&lt;br /&gt;* Can you track at the minute resolution where you time went?&lt;br /&gt;&lt;br /&gt;I don't believe anyone could say yes to those questions. The cause, I would assume, is that the data wasn't even captured correctly, if at all. If we have some sort of a computerized device which can help us to capture those data accurately, that would be great. &lt;br /&gt;&lt;br /&gt;Imagine a world where you have debit or credit cards which categorizes your spending. It is the only "cash" you have. Consequently, all your purchases will be recorded, indirectly providing you with an audit trail of your spendings. At any time, you can pull that data out and refine them to a personal financial report, which you can then use a financial analyzer program to help you find out how you can improve your spendings.&lt;br /&gt;&lt;br /&gt;I believe the data will also be useful for tax calculation. You don't have to keep your receipts (if they exists then) at all.&lt;br /&gt;&lt;br /&gt;How about RF radios? Radios annoy me sometimes. When I travel to other states in my car, I always have to suffer to loss of my favorite radio stations. This could be due to the fact that I do not know the frequency which my favorite stations broadcast at in other states. It could be due to the fact that they are not available in that state as well.&lt;br /&gt;&lt;br /&gt;If all radio stations broadcast using the Internet as the backbone, I just need a domain name to locate them. I don't have to remember their frequency. Now, I have to maintain a mapping of frequency numbers to radio stations. With a domain name, I just have to remember a name.. and it doesn't change wherever I am in the world.&lt;br /&gt;&lt;br /&gt;Having said all that, I'd like to say that I'm already happy with the current state of the integration of technology in our daily lives. It's just that I dream of Utopia sometimes.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-4531829399657225801?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/4531829399657225801/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=4531829399657225801' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4531829399657225801'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/4531829399657225801'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2007/06/technology-technology-o-technology.html' title='Technology, Technology O&apos; Technology Tonight'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-8133367701968937553</id><published>2007-03-10T11:21:00.001+08:00</published><updated>2008-07-26T18:10:04.857+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='leadership'/><title type='text'>Are You A Good Team Leader?</title><content type='html'>Being a team leader involves more than just managing your resources and getting deliverables out. I believe as a team leader, you need to do good in the following items.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;span style="font-size:130%;"&gt;Know your team well&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;Knowing your team well allows you to detect problems and form actions on them before they develop into something that can really wreck the team.&lt;br /&gt;&lt;br /&gt; * Do your team members know what are your plans for them?&lt;br /&gt; * Do your team members know what you are doing?&lt;br /&gt; * Do you know what your team members are doing?&lt;br /&gt; * Do you know what are the personal goals of your team members?&lt;br /&gt; * Do you know what kind of problems your team members are having?&lt;br /&gt; ** Are they overloaded with work?&lt;br /&gt; ** Are they finding it difficult to learn about some aspects of their work?&lt;br /&gt; ** Are they cooperative with each other?&lt;br /&gt; ** Are they motivated?&lt;br /&gt; ** Do they feel appreciated?&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;&lt;br /&gt;A good team leader should always have answers to the above questions. It's not really important what are the answers to the questions above. The important thing is to have answers to those questions.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;span style="font-size:130%;"&gt;Good Communication Skills&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;Open communication is important in a team. As a team leader, you need to ensure that your team members are &lt;span style="font-weight: bold;"&gt;willing to talk&lt;/span&gt; to you about their problems. I have to admit, that it is impossible for team leaders to ask if their team members are having problems all the time. It's just too time consuming and a lot of that effort will be wasted. Therefore, having your team members to tell you of their problems by themselves is important.&lt;br /&gt;&lt;br /&gt;A lot of times, team members do not approach their leaders because they feel that their leaders will smash their skulls with a crowbar if they "give" another problem to him or her. Simply said, team members feel that their leaders are not approachable and it could be due to many reasons. Needless to say, the classic reason is "my team leader looks so stressed up. He must be really busy. I'd best not bug him".&lt;br /&gt;&lt;br /&gt;Might I add, some team members actually fear that once they report a problem, they will become a problem in the team leader's eye. If this exists in your company, address that immediately! It's pretty severe if your team has something like that going on.&lt;br /&gt;&lt;br /&gt;As a team leader, you need to find ways to make yourself approachable! For example, if you really have a scary facial expression when you are stressed up, talk to your team. Ensure them that you are approachable even if you look otherwise. Facial expression is something which is really hard to control and talks will create some understanding between yourself and your team.&lt;br /&gt;&lt;br /&gt;Try to make an effort to small talk with your team members. It encourages them to speak to you. Slowly, they will feel more comfortable talking to you and find you much easier to approach when they have problems which they feel that you can help them with. In addition to small talks, occasional outings would be useful as well. However, it is worth to note that team leaders should &lt;span style="font-weight: bold;"&gt;never force an outing&lt;/span&gt;. It leads to really severe counter-effects.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;span style="font-size:130%;"&gt;Pro activeness&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;A good team leader is someone who is pro active. While you can be the most pro active person when it comes to follow ups on external matters, it doesn't really mean you are also someone that is pro active on internal matters. &lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;Your team members might have reported problems to you (kudos to you, you've got your team rollin') and you've taken some actions to correct the problems. However, did you follow up on the actions? Do they really make an improvement? Are your members happy with the situation?&lt;br /&gt;&lt;br /&gt;Another area that team leaders should be pro-active in are team updates. From time to time, a good team leader should call for update meetings. Some of the things that should always be included in the agenda of the meetings are:&lt;br /&gt;&lt;br /&gt;* Updates of the company, especially information that probably was not relayed through the appropriate channels.&lt;br /&gt;* Your current roles and responsibilities&lt;br /&gt;* The plans for your team members&lt;br /&gt;&lt;br /&gt;As a team leader, you need to be pro-active on internal matters such as those mentioned in the previous paragraphs. It helps to create a culture of pro activeness amongst the team members. Utilize your influence on the team!&lt;br /&gt;&lt;br /&gt;The first step of learning from mistakes is to actually admit them. All the best, team leaders!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-8133367701968937553?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/8133367701968937553/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=8133367701968937553' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8133367701968937553'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8133367701968937553'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2007/03/are-you-good-team-leader.html' title='Are You A Good Team Leader?'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-8828580949662897943</id><published>2007-03-08T00:01:00.002+08:00</published><updated>2008-07-26T18:15:24.872+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='best practices'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='design patterns'/><category scheme='http://www.blogger.com/atom/ns#' term='wtf'/><title type='text'>Development Hell And Its Hellknights</title><content type='html'>What "inspired" me to write this blog post was a recent nightmare I had about maintaining a system that was written by amateur developers.&lt;br /&gt;&lt;br /&gt;The nightmare was about me maintaining a system written in the Java programming language which does inventory provisioning. For some odd reasons, orders could not be created in the system. The users were complaining that they saw error OMA-TX-NOPROC. That was the only clue to the problem, and it was buried in 50MB worth of log files.&lt;br /&gt;&lt;br /&gt;After 2 days searching through the log files using a whole lot of log analyzers (and not forgetting antiseptic cream as the clients were trying to strangle me), I finally found the part of the system that was causing the problem. It was indeed a bug. Cool, I thought. All that I needed to do was to open up the source code of the program and correct the mistake. When I asked for the source code, I was told that while they could provide me with it, they couldn't guarantee if that was the version currently in product because they did not keep track of their program codes.&lt;br /&gt;&lt;br /&gt;I proceeded by grabbing a copy from production and decompiling it. It allowed me to see the program souce code. I saw a Java class that was composed of more than 6000 lines of code. Still, not a problem, I thought. I just have to seach for the error code in the source code (which was OMA-TX-NOPROC). What happened after was truly amazing. I found an if-else block worth more than 500 lines that could lead up to that error.&lt;br /&gt;&lt;br /&gt;The nightmare was too scary. I woke up after that.&lt;br /&gt;&lt;br /&gt;You might have come across problems like the one in my nightmare before. You have my sympathy. Really.&lt;br /&gt;&lt;br /&gt;The fact is, the maintenance problems in my nightmare are caused by amateur developers. These are developers that care only about getting their work done, and nothing more. Those are the developers that you want to watch out for.&lt;br /&gt;&lt;br /&gt;Let's analyze the nightmare, shall we?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;50 MB log files&lt;br /&gt;&lt;/span&gt;Obviously, the developers have no idea what to log, and at which level. One could probably try to defend himself/herself by saying that he/she have enabled detailed logging (DEBUG level) in production. I find that silly. While detailed logging will give you more data, it will be so deeply buried that the data generated will become useless. Might I add, if you had to enable detailed logging in production, it really means that the system was not thoroughly tested, therefore, not ready for production. If it still ends up in production, somebody is being ethically incorrect.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Lack Of Code Tracking&lt;/span&gt;&lt;br /&gt;Really, I couldn't imagine how a development project could kick off with the absence of code tracking. Any developer knows the importance of tracking program codes using tools such as CVS or SVN. If you have a completed project which its source code not tracked, I could imagine only two possibilities:&lt;br /&gt;&lt;br /&gt;01) Only one of your developers is working throughout the project. The rest are coffee makers.&lt;br /&gt;02) Your developers are born with unbelievable memorizing capability. All of them remembers how each piece of code looks like and how they changed throughout the project.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Design, Design and Design!&lt;/span&gt;&lt;br /&gt;When there is a block of code with over 20 conditions, that is a sure-fire indication that something is wrong with your developers. I remembered quite some time ago in IRC, I asked an author of a piece of code why he has a 200+ line if-else block. I still remember the reason he gave me. He said, it's more maintainable, as the code is visible within one editor tab. That is definitely valid if he has a screen as tall as he is and he likes to change between crouching, sitting and standing positions while coding.&lt;br /&gt;&lt;br /&gt;His next argument was code readability. I was relieved a bit because he&lt;span style="font-style: italic;"&gt;&lt;span style="font-weight: bold;"&gt; was&lt;/span&gt;&lt;/span&gt;&lt;span style="font-weight: bold;"&gt; aware of code readability&lt;/span&gt;. However, I found out that he created a few Java classes which have lines that span over 200 characters wide. Needless to say, his ability to scroll in editor tabs amazed me.&lt;br /&gt;&lt;br /&gt;In most cases, complex if-else conditions can be simplified by careful usage of methods and polymorphism.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;So what makes a good developer?&lt;/span&gt;&lt;br /&gt;A good developer should have the following personal traits:&lt;br /&gt;&lt;br /&gt;* Has knowledge of or care about design patterns&lt;br /&gt;* Has knowledge of or care about Object Oriented concepts&lt;br /&gt;* Has knowledge of or care about software maintainability - this includes deployment&lt;br /&gt;* Has knowledge of or care about tracking program code&lt;br /&gt;* Has some ability to think for the people who use their work (users)&lt;br /&gt;* Must not have the attitude of "I know all"&lt;br /&gt;* Has the enthusiasm to improve their technical knowledge&lt;br /&gt;* Knows how to use Unix systems&lt;br /&gt;&lt;br /&gt;I'd love to elaborate more on why the above traits are important. However, it's 1:15 am now and I've written quite a long post. So I'll just end here and crash.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-8828580949662897943?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/8828580949662897943/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=8828580949662897943' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8828580949662897943'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/8828580949662897943'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2007/03/development-hell-and-its-hellknights.html' title='Development Hell And Its Hellknights'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-6489325214059118734</id><published>2007-03-07T22:47:00.001+08:00</published><updated>2008-07-26T18:11:16.439+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>10 Things You Don't Know About Me</title><content type='html'>Heeerrreee's the list:&lt;br /&gt;&lt;br /&gt;01) I don't know how to use call waiting&lt;br /&gt;02) I believe land transport is safer than air transport&lt;br /&gt;03) I think Windows suck (especially Vista as they did not improve the UI of the command prompt. Cleartype! Please! Omfg!)&lt;br /&gt;04) I wonder if my neighbors are alive&lt;br /&gt;05) ... or they have been silently abducted by little green men and replaced with lookalike droids&lt;br /&gt;06) I believe I'm in Malaysia because I'm suffering from the Stockholm syndrome&lt;br /&gt;07) I think I am more likely to die in front of my computer than in a car wreck&lt;br /&gt;08) ... and that I'm writing a blog entry&lt;br /&gt;09) ... and my body will be discovered by my alien neighbors a week after I died&lt;br /&gt;10) I think caffeine injection is a cool idea&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-6489325214059118734?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/6489325214059118734/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=6489325214059118734' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/6489325214059118734'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/6489325214059118734'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2007/03/10-things-you-dont-know-about-me.html' title='10 Things You Don&apos;t Know About Me'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-116032762077436307</id><published>2006-10-09T00:39:00.002+08:00</published><updated>2008-07-26T18:12:31.858+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>The Truth Or Not The Truth</title><content type='html'>"If you can't say anything nice, say nothing at all", the voice of the past spoke to me. This is true in most cases, I believe. It's fairly easy to ask people to think before they talk. Something I think that is easier said than done, even to those that requested for such consciousness when people talk to them.&lt;br /&gt;&lt;br /&gt;It is true that if we do not think before we speak, misunderstandings could arise between the message sender and receiver. The results of the misunderstandings could be quite severe, might I add. Although I agree to that, I do not agree fully. To me, when communication errors occur, it is never fair to blame just on one end of the conversation(in our case, the sender). In communication, I believe, it has a lot to do with the receiver as well. What I mean, is that the receiver should be aware of the characteristics of the message sender. Having understand those characteristics, there will be less communication errors.&lt;br /&gt;&lt;br /&gt;If my friends tell me I look like an ass, I would probably laugh my ass off. I would also tell them to stop coming out from me, and they would laugh their ass off. What's happening here is that I understand that those friends are natural idiots, defects of nature (HAHAHAH... kill me!), and I wouldn't take offence in all those "impolite" words that were thrown at me. Basically, I believe, if you're talking to someone that is not so much of a diplomatic talker and he or she said something you think that is not so appropriate, you should not take ill of it. If you do, you're just as good as a communicator that he or she is.&lt;br /&gt;&lt;br /&gt;As human, it is always easy to find faults in everyone besides yourself. I am not saying I do not blame people, in fact, I am saying I do. I have done it in the past, I would probably do it in the future. What's different between me and those people that do not find faults in themselves is that I have an extra person to blame faults at. If you haven't figured it out, that would be myself.&lt;br /&gt;&lt;br /&gt;Oh yeah, I didn't think much when I wrote this blog entry.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-116032762077436307?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/116032762077436307/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=116032762077436307' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/116032762077436307'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/116032762077436307'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2006/10/truth-or-not-truth.html' title='The Truth Or Not The Truth'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-116003287829942173</id><published>2006-10-05T15:18:00.001+08:00</published><updated>2008-07-26T18:12:00.992+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>This is how</title><content type='html'>&lt;pre&gt;&lt;br /&gt;#!/bin/bash&lt;br /&gt;&lt;br /&gt;# Script to update dyndns.org of the external&lt;br /&gt;# IP of the network to map to the domain name&lt;br /&gt;# configured below&lt;br /&gt;&lt;br /&gt;DYNDNS_USERNAME_PASSWORD="test:test"&lt;br /&gt;DYNDNS_SYSTEM="dyndns"&lt;br /&gt;DYNDNS_HOSTNAME="test.dyndns.org"&lt;br /&gt;DYNDNS_IP=""&lt;br /&gt;DYNDNS_WILDCARD="OFF"&lt;br /&gt;DYNDNS_MX=""&lt;br /&gt;DYNDNS_BACKMX="NO"&lt;br /&gt;DYNDNS_OFFLINE="NO"&lt;br /&gt;&lt;br /&gt;IP_OLD=""&lt;br /&gt;IP_NEW=""&lt;br /&gt;&lt;br /&gt;wget -O - -q http://checkip.dyndns.com |  sed -e s/\&lt;.*\ // -e s/\&lt;.*\&gt;// &gt; "/tmp/external-ip.new.txt"&lt;br /&gt;&lt;br /&gt;if [ $? -ne 0 ]; then&lt;br /&gt;  echo "Error: Unable to obtain external IP."&lt;br /&gt;  exit $?&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;IP_NEW=`cat "/tmp/external-ip.new.txt"`&lt;br /&gt;&lt;br /&gt;if [ -f "/tmp/external-ip.old.txt" ]; then&lt;br /&gt;  IP_OLD=`cat "/tmp/external-ip.old.txt"`&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;if [ "${IP_NEW}" != "${IP_OLD}" ]; then&lt;br /&gt;  echo "Updating to ${DYNDNS_HOSTNAME} to ${IP_NEW}"&lt;br /&gt;&lt;br /&gt;  # This is a one liner. &amp;lt;PRE&amp;gt; fucks up my blog site&lt;br /&gt;  RESULT=`wget -q -O - http://${DYNDNS_USERNAME_PASSWORD}&lt;br /&gt;    @members.dyndns.org/nic/update?&lt;br /&gt;    hostname=${DYNDNS_HOSTNAME}&lt;br /&gt;    &amp;system=${DYNDNS_SYSTEM}&lt;br /&gt;    &amp;myip=${IP_NEW}&lt;br /&gt;    &amp;wildcard=${DYNDNS_WILDCARD}&lt;br /&gt;    &amp;mx=${DYNDNS_MX}&lt;br /&gt;    &amp;backmx=${DYNDNS_BACKMX}&lt;br /&gt;    &amp;offline=${DYNDNS_OFFLINE}`&lt;br /&gt;&lt;br /&gt;  echo "Result: ${RESULT}"&lt;br /&gt;&lt;br /&gt;  echo ${IP_NEW} &gt; "/tmp/external-ip.old.txt"&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;exit 0&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-116003287829942173?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/116003287829942173/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=116003287829942173' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/116003287829942173'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/116003287829942173'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2006/10/this-is-how.html' title='This is how'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-115979552067288870</id><published>2006-10-02T20:47:00.001+08:00</published><updated>2008-07-26T18:12:47.626+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='conspiracy'/><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>The Good, The Bad and You</title><content type='html'>I am unsure if you have heard of the good cop/bad cop technique of pursuasion. It exist, and it is very effective, even now. But what is the good cop/bad cop technique? Where is it being used? Let me start off with what is it that is called the good cop/bad cop technique.&lt;br /&gt;&lt;br /&gt;The good cop/bad cop technique is a psychological tool used by two actors on a victim. Why is it called the good cop/bad cop is because of its origins. The technique was used by police for interrogation. One policeman will play the role of the bad cop while interrogating the suspect. He will be very aggressive, insensitive, rude, brute plus any other aggressive pushy attitude that I can't think of now. He will accuse the suspect, intimidate the suspect and in certain extent, hurt the suspect physically (police brutality might have rooted from here).&lt;br /&gt;&lt;br /&gt;The bad cop is most of the time held back by the good cop. The good cop will offer a cup of water to the suspect. Some food perhaps and maybe even a cigarrette. The good cop will attempt to "protect" the violated suspect and tend to be sensitive to the suspect's needs. When the suspect started to accept the "kindness", the good policeman will "advice" the suspect to cooperate.&lt;br /&gt;&lt;br /&gt;Of course, all these make up an exaggerated scenario based on fiction. However, having said that, it still exists in our world, but perhaps, in a more subtle way. This technique is so effective, it is almost natural. My guess is, you've already been victimized by this technique since very long ago. Yeap.. That's your parents. It doesn't end there, however.&lt;br /&gt;&lt;br /&gt;This technique is used by your tutors, teachers, lecturers, friends, employers and even your country. How? Let me give you an example for the last two. Have you ever had a conflict with your immediate superior and somehow it is "resolved" by someone who has higher authority than your immediate superior? If you pay close enough attention to what they both are trying to achieve, you will find similarities. In most cases, your immediate superior is the bad cop. Needless to say, the good one will be that someone with higher authority. As for your country, the good cop is the political organization that is in power currently. The bad cop, would be the opposing organizations. The opposing organizations are means to make the current political organization look good. If they look good, you will keep them in power.&lt;br /&gt;&lt;br /&gt;I don't understand exactly how this mechanism works. However, I see it like this. As an individual, we are very vulnerable. Naturally, we seek protection, especially in numbers. Generally said, for us, numbers exist in the form of allies. So how does this apply to why the technique works? &lt;br /&gt;&lt;br /&gt;The enemy would be the bad cop. When the good cop protects you from the bad cop, you will naturally think that he or she is a potential ally. Naturally, we want to "neutralize" the enemy. With an ally, it would be easier. So in order to acquire an ally, there is a give and take. You accept their "protection" and you give your cooperation. After you have given your cooperation, you might even have the impression that you have an ally as the bad cop stops attacking you. However, that is not the case. It is because they have already gotten what they want and do not need you anymore.&lt;br /&gt;&lt;br /&gt;Therefore, we need to adjust our thinking. Instead of accepting something better (from the good cop), do not accept anything. Be firm on your wants, and not theirs.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-115979552067288870?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/115979552067288870/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=115979552067288870' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/115979552067288870'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/115979552067288870'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2006/10/good-bad-and-you.html' title='The Good, The Bad and You'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-115839669105836510</id><published>2006-09-16T15:26:00.001+08:00</published><updated>2008-07-26T18:12:59.879+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>Miles Travelled But Not Moved</title><content type='html'>The torn shoes dragged another step before they stopped. The ambience started to spin slower. A dry mouth, an aching back, warm skin and two wrinkled hands resting on a weary knees were his only companions. He had run so fast, for so long.&lt;br /&gt;&lt;br /&gt;From a bent position, he slowly moved his exhausted self to sit on the side walk. As he sat on the ground, he let out a sigh of relief. On his left was where he came from, on his right was where he had to go. Catching a breather, he also took time to appreciate the serene environment. Looking to his left, he tried to recall what were the things he had seen.&lt;br /&gt;&lt;br /&gt;Strangely, he could not remember, at least, not clearly. All he could remember were ghastly images of people that he had talked to. There were girls, boys, women, men and even children. He recalled that all of them offered him something. Some of them offered water, some of them offered tissues, some of them offered conversations and some of them even offered memories.&lt;br /&gt;&lt;br /&gt;He remembered he took all of the offers that he received. However, he never kept them. He was running. He had to track forward. He thought if he was to keep them, he would have no more space to keep what in front had to offer. Although he did not know what in front had to offer, he was so sure it was better than what he got. So he just ran, and he ran.&lt;br /&gt;&lt;br /&gt;His thoughts were interrupted. The thirst for water was simply too strong to ignore. With every drop of sweat that runs from his face to his neck, he felt more and more uncomfortable. He had finished his supply of facial tissues. He shook his head. Having ran so far chasing for something better, yet, he did not even have what he initially had.&lt;br /&gt;&lt;br /&gt;Suddenly, there was a gentle tap on his shoulder. He turned his head and saw a dark figure, shadowed by the sun behind it, was offering a bottle of water and a pack of facial tissues. He thought generousity could not have better timing, and took the water and tissues from the generous hands of the silent figure. He felt so gratified.&lt;br /&gt;&lt;br /&gt;After quenching his thirst and dried his face, he felt a lot of better. He felt energized. He looked at the figure again, and said thanks with his usual smile. The figure was unresponsive. He then took a look at his watch and realize that he had spent a lot of time resting. He then stood up and started to run.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-115839669105836510?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/115839669105836510/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=115839669105836510' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/115839669105836510'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/115839669105836510'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2006/09/miles-travelled-but-not-moved.html' title='Miles Travelled But Not Moved'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-115138966910478753</id><published>2006-06-27T14:22:00.001+08:00</published><updated>2008-07-26T18:13:10.584+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>John</title><content type='html'>I've read a recent blog post by a friend. Being only able to read the blog, I guess, it is clear that we haven't talked for quite a while. But nevertheless, the blog post was a great read. The post talked about an uninspiring Joe day turning out to be surprisingly heart warming.&lt;br /&gt;&lt;br /&gt;It details about herself being moody, expecting a surprise to cheer her up perhaps. She described herself spending the day doing nothing special with her significant other. She wrote that she did not get any surprise which she expects otherwise. However, at the end of her post, I think she actually did. Joe was not Joe, simply put.&lt;br /&gt;&lt;br /&gt;The surprises, the places to go, the food to eat doesn't really matter. What truly matters is how the time was spent with someone you care about. It could be really simple things but still end up memorable. It could be just a laugh or two, a nudge, a squeeze, a cooking session, or even just watching a rerun on television. Happiness is quite modest.&lt;br /&gt;&lt;br /&gt;Ordinary is a speciality that the common us do not usually see.&lt;br /&gt;&lt;br /&gt;My friend's post is here:&lt;br /&gt;&lt;a href="http://meikar.blogs.friendster.com/mei_kars_blog/2006/06/ordinary_satisf.html" target="_blank"&gt;Ordinary Satisfying Day&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-115138966910478753?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/115138966910478753/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=115138966910478753' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/115138966910478753'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/115138966910478753'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2006/06/john.html' title='John'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-114576755981727423</id><published>2006-04-23T12:43:00.001+08:00</published><updated>2008-07-26T18:13:26.262+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>A Sunday That Most Of Us Don't Know</title><content type='html'>The weak sunlight is crawling into the room. Sneaking a shine, I know the effort is in vain. No matter what Sunday tried to pretend as, I could more or less see through it. It's hard to be Sunday really. Everyone expects Sunday to be always happy and cheerful. For most of the time, even I expect that. &lt;br /&gt;&lt;br /&gt;However, on this gray day, I've come to understand more of what Sunday has been. Beneath its warming smile, lies a decaying heart. Sunday had always been just a display for our happiness. Unknowingly, we have been forsaking Sunday for a very long time now. Sunday has never been itself. We selfish fools.&lt;br /&gt;&lt;br /&gt;Sunday is unusually quiet today. Too quiet perhaps. The feeling is tranquil. I'm wondering what Sunday is doing now for others. Is it still putting on that fake smile for others who expects it? Does it work?&lt;br /&gt;&lt;br /&gt;Sunday is going away in a while. Hopefully the weekdays and Saturday will treat it better for weeks to come. One day, Sunday will truly shine again.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-114576755981727423?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/114576755981727423/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=114576755981727423' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/114576755981727423'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/114576755981727423'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2006/04/sunday-that-most-of-us-dont-know.html' title='A Sunday That Most Of Us Don&apos;t Know'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-114105100836361745</id><published>2006-02-27T21:20:00.001+08:00</published><updated>2008-07-26T18:13:40.480+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>The Right Turn</title><content type='html'>Driving down a faceless highway, my radio was playing lightly. The digital display showed 94.5, the frequency of one of my favourite radio stations. The car felt spacious, as it always has been. It was not because my car was big, but perhaps, it was because of how small I felt most of the time.&lt;br /&gt;&lt;br /&gt;Ahead were a pile of cars that stopped before the traffic lights. Gently, I pressed on the brakes to slow the car down to a halt. My car stopped eventually. Resting my right elbow window ledge and my chin lying for support from my right palm, I gazed to my right.&lt;br /&gt;&lt;br /&gt;I started to think how things would be different if I had said and done different things today. Wouldn't it be good if you're given a choice everytime you had to say or do something? How about throwing in a pause point everytime that happens? A rewind button on top of that would be great. Then I would be able to say and do all the right things.&lt;br /&gt;&lt;br /&gt;But then, what is right and what is wrong? Would it be right to rewind and erase your mistakes and then simply make the right one, without understanding and suffering the consequences? Would it be wrong to try to be right?&lt;br /&gt;&lt;br /&gt;The sound of a car horn made me snapped out of my trance. I signal right. Then I made a right turn.&lt;br /&gt;&lt;br /&gt;I always have.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-114105100836361745?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/114105100836361745/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=114105100836361745' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/114105100836361745'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/114105100836361745'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2006/02/right-turn.html' title='The Right Turn'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-114085843043681478</id><published>2006-02-25T16:58:00.001+08:00</published><updated>2008-07-26T18:13:54.879+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>The Autumn Graveyard</title><content type='html'>The darkness slowly faded. Manifesting from the center of the darkness, a glare was evident. The glare slowly forms a distorted image. As if looking at a picture located at the bottom of a clear rippling lake from a bird's eye view, the image appeared as if the world was shaking.&lt;br /&gt;&lt;br /&gt;The rippling started to calm down. What a view. It was a mute image of a clear blue sky accompanied by clusters of white clouds and the sun behind them smiling down proudly. Suddenly, a shadow dropped over the sun. It was a tree leaf, gracefully falling down from the sky, as if to decrease the glare that the sun had always boasting about. The leaf touched the ground and gave birth to the horizon.&lt;br /&gt;&lt;br /&gt;The ground was carpeted by millions of leaves just like the one that had just fallen from the sky. Tracing the horizon, there were no mountains, no hills, no trees, no villages. The only animate beings in this strange land are shadows. Even then, the shadows seem more alive that the objects that cast it.&lt;br /&gt;&lt;br /&gt;The place looked like a place of nothing. The horizon always keeping a distance. From time to time, the shadows made faces. The effort seemed to be in vain, like how one desperately cracks a joke when no one is listening. What was amazing is that the shadows never stopped.&lt;br /&gt;&lt;br /&gt;This is a timeless place liberated from the problems of the day called The Autumn Graveyard.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-114085843043681478?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/114085843043681478/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=114085843043681478' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/114085843043681478'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/114085843043681478'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2006/02/autumn-graveyard.html' title='The Autumn Graveyard'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-114018548119735863</id><published>2006-02-17T22:02:00.001+08:00</published><updated>2008-07-26T18:14:08.577+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>Emptiness</title><content type='html'>&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-114018548119735863?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/114018548119735863/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=114018548119735863' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/114018548119735863'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/114018548119735863'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2006/02/emptiness.html' title='Emptiness'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-113885954074538410</id><published>2006-02-02T12:56:00.001+08:00</published><updated>2008-07-26T18:14:36.881+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>Cool Breeze</title><content type='html'>The cold winds blew across the vast landscape of rocky mountains. The night had somehow exaggerated the coldness. It seemed that the winds were so cold that it could trim the mountains. The deep ravines between mountains seemed like scars to what was once a warm place filled with life. The air that once smelled like hope was tainted with the smell of death.&lt;br /&gt;&lt;br /&gt;In the middle of the landscape, stood a mountain taller than the rest. It dwarfed other mountains that surrounded it. Near the summit, there was one cliff like none. Before the cliff, there was a tunnel. The walls of the tunnel were made up of trees. The trees from both side of the walls curved toward each other, as if needing support, to form the roof of the tunnel.&lt;br /&gt;&lt;br /&gt;A sound echoed from the far end of tunnel. It was Jimmy. Somehow, he found his way up there. It was the sound of his footsteps. Jimmy stepped out from the shadows into the dim moon light. Soon, he was at the edge of the mountain.&lt;br /&gt;&lt;br /&gt;His face looked expressionless. There was no signs of emotions of any kind. His arms were bleeding from the cuts that he had endured in his journey up the mountains. The blood was fresh, as if he was cut just. Jimmy did not bother with them. He slid his right hand into his back pocket and took out a burnt penny.&lt;br /&gt;&lt;br /&gt;Jimmy stared at the penny for a moment and then gazed at the dim stars. For a moment, it seemed that Jimmy gave a faint smile as his eye was affixed to the wonders of the universe. Jimmy then looked at his right palm that held the penny. This time, with a more solid smile.&lt;br /&gt;&lt;br /&gt;Jimmy walked one step closer to the edge of the mountain. He pulled his right arm back, with the penny in his fist. He threw the penny over the cliff. Jimmy looked on as the penny flew into the air, giving its last shimmer. As the penny fell from the sky, before it was about to disappear into the darkness below, Jimmy jumped. His right arm stretched outward towards the penny. Jimmy descended into the darkness.&lt;br /&gt;&lt;br /&gt;Jimmy finally found happiness.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-113885954074538410?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/113885954074538410/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=113885954074538410' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/113885954074538410'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/113885954074538410'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2006/02/cool-breeze.html' title='Cool Breeze'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-113672637192387267</id><published>2006-01-08T19:59:00.001+08:00</published><updated>2008-07-26T18:15:45.071+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>Garden walk</title><content type='html'>It was dark. In the vast night, it seemed to Jimmy that the only source of light came from the stars. It was quiet. So quiet, that craoks of frogs was clearly heard. The wind whispered as Jimmy silent thought about how he ended up in the woods, surrounded by trees that seemed to be crawling closer to him every second. However, he was unafraid. He was too disturbed over an incident that had happened.&lt;br /&gt;&lt;br /&gt;He could not grasp the slightest of understanding. Clueless, as if a new born baby, Jimmy turned around. A path was paved by footsteps before him. He knew, at that time, he had the luxury of trailing backwards on the path he had walked. Slowly, guided by the stars, he did just that.&lt;br /&gt;&lt;br /&gt;As Jimmy walked, he came to a split in the path. Just ahead, before the split, there were more footsteps. Jimmy could even recognize a few of them. Something shimmered in the corner of Jimmy's eye. He saw a penny. Jimmy went over to pick it up. It was covered with mud. Jimmy squat down and wiped the penny with his shirt. After much cleaning, a part of it was still black in color, as if it was burned. Then he remembered, he had a penny just like this one. That must be his, he must've dropped it.&lt;br /&gt;&lt;br /&gt;The memories rushed in as Jimmy gazed at the coin. It was the coin that he did not flip. He remembered that when he reached the split in the path, he was far too proud to have his path decided by a penny and threw it away. Ironically, while he had defined his own path, there he was, trailing back and came to the penny.&lt;br /&gt;&lt;br /&gt;Looking from left to right, he saw footsteps deviating from the path he had walked. Those must be of the ones that had flipped their coins, he thought. The deviated paths looked rough, which reminded Jimmy of why he did not take it. Suprisingly, the deviated paths looked to be more popular.&lt;br /&gt;&lt;br /&gt;Jimmy was frustrated. Jimmy still could not quite understand what drove the maniacs to walk the rough terrain. To his frustation, it started to drizzle. The tiny drops of rain felt like  bullets crushing into Jimmy's skull as he sank deeper into his thoughts. "They took a big risk", Jimmy said to himself. "Do they not understand what they might lose? Oh, your dear life!".&lt;br /&gt;&lt;br /&gt;Then suddenly, Jimmy finally understood. He looked at the penny he held in his palm and started to smile. "Why of course," Jimmy said to himself again. "They are saying the same thing." Jimmy then stood up. He flipped the penny into the air and as it fell down from the moon, Jimmy catched it with his palm and closed it. He slid the penny to his back pocket and turned around, facing the direction he just came from and walked forward.&lt;br /&gt;&lt;br /&gt;"At least now I have a penny.", said Jimmy as he disappeared into the shadows of the night.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-113672637192387267?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/113672637192387267/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=113672637192387267' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/113672637192387267'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/113672637192387267'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2006/01/garden-walk.html' title='Garden walk'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-113505160829046855</id><published>2005-12-20T11:33:00.001+08:00</published><updated>2008-07-26T18:16:01.670+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>Of A Day's Confession</title><content type='html'>2 in the morning again. Having reached home, yet again exhausted. I slid my key into the pad lock of my house door. A loud clang seems to be the only sound in the world when the pad lock was opened. Despite the loudness of the sound, no one was awakened by it.  I removed the lock and slid the door to my left with the few ounces of energy I was left with. The opening was just barely enough for my the slide through it. "Yet another one of those days", I thought. &lt;br /&gt;&lt;br /&gt;I locked the door behind me. Seems like the house was illuminated only by the oil lamp on the altar at the end of the living room. "Maybe she's the only light.", I thought to myself, chuckling. The deafening silence in the room gave me the impression that everything was asleep. Tic-toc-tic-toc. The sounds of the clock reminded me that I only have a few hours to sleep. "Someday, I'm going to count on you", I thought to myself again.&lt;br /&gt;&lt;br /&gt;I walked towards the flight of stairs leading to my bedroom. It seems to have grown. Slowly, I climbed the stairs. Reaching the top, it felt almost victorious. Silly me. It seemed that gravity was pulling stronger with every step I took as I walked towards my bedroom. After a short while, I reached my bedroom door. Looking at my bed, I gave out an inaudible sigh. "The beginning and the end", I thought to myself.&lt;br /&gt;&lt;br /&gt;I switch on the air-conditioner. It was loud. It has always been loud. Seemed like I'm not the only one feeling like a thousand years old. I sat a while on my bed. "Tomorrow is another day, but just. I better sleep now so that I can have today tomorrow".&lt;br /&gt;&lt;br /&gt;I went to bed shortly after. Indeed, I was right.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-113505160829046855?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/113505160829046855/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=113505160829046855' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/113505160829046855'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/113505160829046855'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2005/12/of-days-confession.html' title='Of A Day&apos;s Confession'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-113391980145217097</id><published>2005-12-07T09:20:00.001+08:00</published><updated>2008-07-26T18:16:13.771+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>Life In An Infinite Loop</title><content type='html'>Life is a miraculous gift of unknown cause. Its up and downs blending together in an endless chain of cause and effect which we do not understand, and perhaps never will. Until in a point in time (if there is ever a single unit of time), one can't help but wonder what life is all about. The question of why are we here.&lt;br /&gt;&lt;br /&gt;I tried to view it from my daily life. I could not tell from how life is different from yesterday to  today, or from last year to this year. It has always been the same. I get up from bed. I then get ready for my tasks. I try to finish my tasks. I exhaust myself. I get a little leisure. I go back to bed, preparing for the same thing to happen again tomorrow.&lt;br /&gt;&lt;br /&gt;I can never escape from the thought of what I am going to do tomorrow. It seems strange. Because I already know, at least, what I am suppose to do tomorrow. A sign of hope for a break from the vicious loop? I would guess so. &lt;br /&gt;&lt;br /&gt;I feel jaded now. After so many days, so many hours of the same old thing. My nerves tell me I'm too much of a coward to end my suffering. I can't endure the pain prior to the ultimate relief. Therefore I am still here, with a fool's hope, of something good and different that might happen tomorrow.&lt;br /&gt;&lt;br /&gt;The Utopian idea exists, but will always just.&lt;br /&gt;&lt;br /&gt;Life is just a poison we take everyday.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-113391980145217097?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/113391980145217097/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=113391980145217097' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/113391980145217097'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/113391980145217097'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2005/12/life-in-infinite-loop.html' title='Life In An Infinite Loop'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-112400986966687434</id><published>2005-08-14T15:54:00.001+08:00</published><updated>2008-07-26T18:16:34.487+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>Off topic: Hanging On A Thread</title><content type='html'>&lt;span style="font-family: georgia;"&gt;This is about Java programming. For those who are not interested in such discussions, I think, this post will not be able to spark any interest in you.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: georgia;"&gt;Synchronization has always been a topic of discussion for any level of Java programers. While the construct of synchronization can be easily explain in a sentence or two, exemplary usage are usually rare.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: georgia;"&gt;First of all synchronization is not as slow as we think it is. And in any system where there is concurrent access, the system cannot escape from synchronization. The funny thing is, while we most of the time try to escape the usage of the &lt;/span&gt;&lt;code style="font-family: georgia;"&gt;synchronized&lt;/code&gt;&lt;span style="font-family: georgia;"&gt; keyword, we still use it all the time. This can be seen whenever there is usage of synchronized (internally) collections. Some of them are Vectors and Hashtables. And most of the time, we iterate through the collection. For example (typical collection iteration code)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;for (int i = 0; i &amp;lt myVector.size(); ++i) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;Object o = myVector.get(i);&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This would result in a lot of synchronized calls. If the collection contains 1000 elements, there will be 2000 synchronized method calls. This is because Vectors are synchronized collections. We are not going to discuss about the usage of Vectors here, but generally, the usage of Vector in my opinion has been deprecated by ArrayLists with proper synchronization. Take a look at the code snipplet below. This will only result in 1 synchronized operation call because ArrayLists are not internally synchronized&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;synchronized (myArrayList) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (int i = 0; i &amp;lt myArrayList.size(); ++i) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Object o = myArrayList.get(i);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Also, despite Vectors are internally synchronzied, they are not suitable for usage in a multithreading system. Refer to the first code snipplet. Imagine if the Vector contains 3 elements. Therefore, the loop will iterate 3 times. However, that is not always the case. Elements can be added or removed from the collection between line 1 and 2. The executing thread can be preempted. Imagine thread 1 (Thread A) is now at the 3rd iteration, where the variable &lt;code&gt;i&lt;/code&gt; now has the value of &lt;code&gt;2&lt;/code&gt;, after the first line, we would assume that we would be getting the 3rd element (index 2) in line 2. Wrong. Thread preemption can be done between line 1 and 2. If another Thread (Thread B) removes an element after Thread A has been preempted, when Thread A executes again, an ArrayIndexOutOfBoundsException would be thrown because Thread A is trying to get the third element which doesn't exist (the Vector's size has been reduced by 1 by Thread B). This is just a trivial example.&lt;br /&gt;&lt;br /&gt;However, with the second code snipplet, the window of opportunity for the error to occur is impossible with the correct synchronization technique. We have exclusive access to the ArrayList for the duration enclosed by the synchronized block. If another Thread tries to obtain the lock on the ArrayList, the thread will block. However, notice that I've said with the correct synchronization technique. This will only work if both Threads tries to lock on the ArrayList. If Thread B doesn't agree on the protocol, errors or anomalies can happen.&lt;br /&gt;&lt;br /&gt;While we are still on the topic of threading in a Java environment, some of us extend Thread to produce the effect of multithreading. While this approach works, this level of inheritance is most of the time simply unneeded. Extending Thread is only needed if we need to change the behavior of Threads. If we are to just execute a task in another Thread, we should implement Runnable. By doing this, it gives us &lt;em&gt;options&lt;/em&gt; on how code can be improved. We can reuse Runnable implementations by using the correct pattern, for example, a EmailTaskFactory that returns EmailTasks which implement Runnable where the same instances can be reused 2 or more times. The EmailTaskFactory can be implemented in a variety of ways to accomplish that.&lt;br /&gt;&lt;br /&gt;But when should we multithread? Multithreading is especially useful for operations that would most likely block. The most obvious scenario is IO operations which typically refer to disk IO and network IO. With threads, we can maximize the throughput. For instance, we have a bandwidth of 100Mbps from the box running the VM and the FTP server. We are to upload files to the FTP server. In typical approaches, we would be sequentially uploading a file at a time. This can result to unneeded prolongation. FTP is built on TCP. Therefore, it inherits the slow start network IO inherent in TCP. Efficient network bandwidth usage can only be achieved if the file is sufficiently large, when the TCP buffer windows optimize their sizes as packets come in and goes out. If we're transferring a lot of small to medium files sequentially, we would be wasting time.&lt;br /&gt;&lt;br /&gt;While most FTP servers only allow one data connection per FTP session, they usually allow multiple sessions. On the Java side, if we're transferring a lot of small to medium files, it would tremendously improve execution time if we spawn threads that will initiate sessions with the FTP servers for file transmission. This is not because we have better algorithm or better memory management (in fact, it would use more memory). This is because we're less affected by blocking operations.&lt;br /&gt;&lt;br /&gt;I've written a long post. Until I feel like writing again. Chiaoz.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-112400986966687434?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/112400986966687434/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=112400986966687434' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/112400986966687434'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/112400986966687434'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2005/08/off-topic-hanging-on-thread.html' title='Off topic: Hanging On A Thread'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-112307963924421720</id><published>2005-08-03T21:58:00.001+08:00</published><updated>2008-07-26T18:16:48.857+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>Off topic: Intrinsic</title><content type='html'>There was an interesting discussion, though brief, about stress management with my colleagues today. I sincerely believe that we have our own ways to relax. I argued that sleeping is my way about it and my colleagues believed otherwise. They somehow implied that going out would be the best way. Undeniably, they are right. Then again, so am I.&lt;br /&gt;&lt;br /&gt;My way of relaxing is to build up the pressure, regardless of source before ultimately letting it go. It has similarities with the theory of the art of shitting proposed by a friend of mine. Those of you who have heard of that, whether from him directly or from me, will know exactly what I'm talking about. The true value of relaxation is only expressed when the relief comes at the peak of the stress levels. With that, it deprecates temporal reliefs at **any** time before that.&lt;br /&gt;&lt;br /&gt;But how does that relate to sleep? Sleep for me is a worthless temporal relief. It does nothing more than to prepare me for acceptance of more pressure, which, is what I need. To build up the pressure. To feel angered, frustrated, confused, depressed and all other negative emotions, in their most purest form, before letting it go, in one night, in a uniform music, in harmonized movements in the name of trance. One night, one life no pressure.&lt;br /&gt;&lt;br /&gt;20th is the night. Hopefully, I'm there at the main arena in R3 to feel....&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-112307963924421720?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/112307963924421720/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=112307963924421720' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/112307963924421720'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/112307963924421720'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2005/08/off-topic-intrinsic.html' title='Off topic: Intrinsic'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-112230059809833486</id><published>2005-07-25T21:50:00.001+08:00</published><updated>2008-07-26T18:17:35.095+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><category scheme='http://www.blogger.com/atom/ns#' term='music'/><title type='text'>Off Topic: Top 10 Trance</title><content type='html'>I love trance. I simply adore it and here are my top 10.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;DJ Tiesto - Suburban Train&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Jose Amnesia - The Eternal&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Veracocha - Carte Blanche&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Saltwater - The Legacy&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Gouryella - Ligaya&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Armin Van Buuren - Communication&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Armin Van Buuren - Burned With Desire&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Oceanlab - Satellite&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Andain - Beautiful Things&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Paul Van Dyk - Crush&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-112230059809833486?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/112230059809833486/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=112230059809833486' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/112230059809833486'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/112230059809833486'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2005/07/off-topic-top-10-trance.html' title='Off Topic: Top 10 Trance'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-111988051663599461</id><published>2005-06-27T21:10:00.001+08:00</published><updated>2008-07-26T18:16:54.981+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>Propaganda</title><content type='html'>The attempt of establishing a propaganda is the very act of denying one of freedom. When practised long enough, the doctrine or set of principles became systemic to our lives, wrapping us in an invisible shield that forbids us from seeing beyond it. Hence, we became oblivious and would accept further propaganda injections without objection.&lt;br /&gt;&lt;br /&gt;Propaganda, like the greatness of democracy, in my view, has been overly celebrated. Democracy is hypocritical. The idea of democracy was that people are able to elect individuals like themselves to look after the people's welfare, and the people have power to replace the representative if the objective was not met. To me, it was a deliberate act to divide the power of change, so no individual conscience can ever have enough influence to provoke a rebellion. The idea itself provides a facility to create a delusion to people that choices made are always of the best compromise, when there is absolutely no compromise at all. Think about this. Are you ever convinced that you can make a change? When you made a choice, was it really yours?&lt;br /&gt;&lt;br /&gt;Democracy, therefore, is not just an idea. It is just a mean to achive total power.&lt;br /&gt;&lt;br /&gt;Propagandas can get into our minds if we're too submissive. Therefore, I like to look at things in different perspectives. Most of the perspectives however, are deemed negative by those around me. Rather than feeling discourage, I am encouraged by the fact that I am able to break from what it is perhaps a propaganda trying to shroud my conscience. Still being to relate to reality, I find myself in the perfect balance.&lt;br /&gt;&lt;br /&gt;I desperately urge those gifted with 'eccentric' thoughts like these be shared here.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-111988051663599461?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/111988051663599461/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=111988051663599461' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/111988051663599461'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/111988051663599461'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2005/06/propaganda.html' title='Propaganda'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-111940698151330773</id><published>2005-06-22T10:12:00.001+08:00</published><updated>2008-07-26T18:17:13.603+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>Mission statement</title><content type='html'>"Freedom is the freedom to say 2+2=4. If that is granted, all else follows.". An excerpt from a book written by George Orwell named 1984. The book itself, in my interpretation, doesn't specify what freedom is. However, the book describes what situations could be like without it.&lt;br /&gt;&lt;br /&gt;Enslavement in the context of this site is not limited to physical enslavement. It will include any form of enslavement, be it physical or not, submissive or forced, the paradoxy of freedom and perhaps the price of freedom if it is capable of being defined at all.&lt;br /&gt;&lt;br /&gt;In this site, I write my thoughts on what freedom is. Here I grimly hope that sympathetic eyes will read my thoughts and share insights with me in a distributed effort to wage a war against enslavement and win it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-111940698151330773?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/111940698151330773/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=111940698151330773' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/111940698151330773'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/111940698151330773'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2005/06/mission-statement.html' title='Mission statement'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13841310.post-111935935034882820</id><published>2005-06-21T21:08:00.001+08:00</published><updated>2008-07-26T18:17:18.251+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random babbles'/><title type='text'>First blog!</title><content type='html'>&lt;span style="font-family:courier new;"&gt;First blog! Whee&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/13841310-111935935034882820?l=nuhk.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nuhk.blogspot.com/feeds/111935935034882820/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13841310&amp;postID=111935935034882820' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/111935935034882820'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13841310/posts/default/111935935034882820'/><link rel='alternate' type='text/html' href='http://nuhk.blogspot.com/2005/06/first-blog.html' title='First blog!'/><author><name>David Chui</name><uri>http://www.blogger.com/profile/15633853802656853463</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_3wFdvCO12KA/Svwc9zbxNiI/AAAAAAAAABw/UIiLpTlY4CM/S220/dchui-small.png'/></author><thr:total>0</thr:total></entry></feed>
