Feeds:
Posts
Comments

Archive for January, 2012

ZT: 20 Pieces of Programming Wisdom

  1. Set a duration of how long you think it should take to solve a problem – C’mon, admit it! I’m just as guilty as the next programmer. I’ve seen programmers sit in front of a monitor for eight hours at a time trying to solve a particular problem. Set a time table for yourself of 1 hour, 30 minutes, or even 15 minutes. If you can’t figure out a solution to your problem within your time frame, ask for help or research your problem on the Internet instead of trying to be super-coder.
  2. A language is a language is a language – Over time, once you understand how one language works, you’ll notice similarities between other languages. The language you choose should provide you with a suitable “comfort” level, the ability to produce efficient (and clean) code, and, above all, allow the language to suit the project and vice-versa.
  3. Don’t over-”design pattern” applications – Sometimes it’s just easier to write a simple algorithm than it is to incorporate a singleton or facade pattern. For the most part, it even allows for cleaner, understandable code. 🙂
  4. Always backup your code – I’ve experienced a complete hard drive failue and lost a lot of code when I was younger and felt horrible because of what had happened. The one time you don’t back up your data may be the one time where you have a strict deadline with a client and they need it tomorrow. Source code/version control applies here as well.
  5. You are not the best at programming. Live with it. – I always thought that I knew so much about programming, but there is always someone out there better than you. Always. Learn from them.
  6. Learn to learn more – With number five explained, I’ve always had a magazine or book in my hand about computers or programming (ask my friends, they’ll confirm). True, there is a lot of technology out there and keeping up with it is a fulltime job, but if you have a smart way of receiving your news, you’ll learn about new technology every single day.
  7. Change is constant – Your knowledge of technology and/or programming should be similar to how you treat stocks: Diversify. Don’t get too comfortable with a particular technology. If there’s not enough support for that language or technology, you might as well start updating your resume now and start your training period. My general rule of thumb that has kept me going? Know at least two or three languages, so if one dies off, you have another one to fall back on while you train for a new technology.
  8. Support Junior – Assist and train the junior/entry-level developers on good programming guidelines and techniques. You never know…you may move up in rank and you’ll feel more confident having personally trained and prepared them for their next position.
  9. Simplify the algorithm – Code like a fiend, but once you’re done, go back through your code and optimize it. A little code improvement here and there will make support happier in the long run.
  10. Document your code – Whether its documenting a Web Service API or documenting a simple class, document as you go. I’ve been accused of over-commenting my code and that’s something I’m proud of. It only takes a second to add an additional comment line for each 3 lines of code. If it’s a harder technique to grasp, don’t be afraid to over-comment. This is one problem most architects, backup coders, and support groups don’t complain about if you’ve done your job right.
  11. Test, Test, Test – I’m a fan of Black Box Testing. When your routine is finished, your “stamp of approval” period starts. If you have a Quality Assurance department, you may be talking more to them than your project manager regarding errors in your code. If you don’t test your code thoroughly, you may develop more than code. Possibly a bad reputation.
  12. Celebrate every success – I’ve met a lot of programmers who have conquered headache-style problems with a great programming technique and celebrated with a fellow programmer by doing the “shake”, the high-five, or even a “happy dance.” Everyone has enlightening periods in their life and even though that one happy coder asked you to come and see his extraordinary piece of code and you’ve seen that one piece of code over 100 times in your experiences, celebrate the success of a fellow developer for the 101-st time.
  13. Have Code Reviews Frequently – On projects and personally. In the company, you will always have code reviews of how well you coded something. Don’t look at it as people crucifying your coding style. Think of it as constructive criticism. On the personal front, review your code and always ask, “How could I have done it better?” This will accelerate your learning and make you a better programmer.
  14. Reminisce about your code – There are two ways to looking at old code: “I can’t believe I wrote this code” and “I can’t believe I wrote this code.” The first statement is often of disgust and wondering how you can improve it. You’d be surprised at how old code can be resurrected into a possible and better routine, or maybe even an entire product. The second statement is of amazement and achievement. Developers have their one or two project code achievements that they completed and had everyone standing up and taking notice. Again, based on your excellent coding ability, you could take those past routines or projects and update them into a better product or idea.
  15. Humor is necessary – In my 20 years of development, I have never met a programmer who hasn’t had a decent sense of humor. Actually, in this industry, it’s a requirement.
  16. Beware the know-it-all, possessive coder, and the inexperienced coder – Humble yourself when you meet these types of coders. The know-it-all tries to upstage you instead of working as a team player, the defensive coder created code that he doesn’t want to share with anyone, and the inexperienced coder constantly asks for assistance every ten minutes where the finished code developed is yours, not theirs.
  17. No project is ever simple – I’ve been asked by friends, family, and associates to just “whip something up for me.” To “whip” up a program or web site, it takes planning from both parties to complete something that both sides can appreciate. If someone needs a 3-page web site with Microsoft Access from the start, it winds up becoming a 15-page web site with SQL Server, a forum, and a custom CMS (Content Management System).
  18. Never take anything for granted – If you take on a simple project, you may think that a certain section will be easy to complete. Don’t think that even for a moment. Unless you have a class, component, or piece of code already coded…and has been tested thoroughly…and is in production from an existing project, don’t think it will be easy.
  19. Software is never finished – A fellow programmer once told me that software is never finished, it’s “temporarily completed.” Sound advice. If the client is still using a program you wrote and has stood the test of time, chances are, you are still updating it, which isn’t a bad thing. It keeps you working. 🙂
  20. Patience is definitely a virtue – When clients, friends, or family members use a PC, they get frustrated and proceed to hit a component of the PC or storm off. I keep telling everyone, “you are controlling the computer not the other way around.” You need to have a certain level of patience for programming computers. As soon as programmers understand what they did wrong, they look at it from the computers point of view and say, “Oh, that’s why it was doing that.”
Advertisements

Read Full Post »

Design Pattern笔记:Decorator

这可能是Design Pattern里最简单的之一,但是上次JobHunting版有人问时,仔细想想,发现有些东西其实自己是没想清楚的,所以又读了一遍。

=================================================================

Decorator的核心就是一个operation, 经过一条链来处理,链上的每个环节都可以影响/处理最后的输出。这条链是可以动态增加的。

从结果看,你希望达到的效果就是:
decoratedObj.act()会依次call链上的每一个decorator的act()

要达到这种效果,那每个decorator需要知道:
1. 最初的object的一些公开属性,这可以通过传递object实现
2. 链的上一层处理完的结果,这个通过在act()里递归实现
所以,Decorator需要wrap原来的object, 这个既可以在constructor里实现,也可以通过set***()函数实现,这点跟Inversion of Control(IoC)的实现是一个意思。但个人以为,写在constructor里,实现更清晰易读。

其次,Decorator需要实现act(), 这个实现就是完成自己的任务,然后调用链的上一节
点的act(),形成递归。注意:这个次序是可以改变的,完成自己的任务可以在parent.
act()之前,也可以是之后。

写成code, 大致就是:
public class Decotorator {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
public void act() {
this.component.act();
}
}

把那个JobHunting版的例子改一下就是:

public class Person {
private String name;

public Person() {}

public Person(String name) {
this.name = name;
}

public void show() {
System.out.print(“Decorated person is ” + this.name + “.”);
}

public static void main(String[] args) {
Person p = new Person(“Alice”);
Finery decorated = new TShirt(new BigTrouser(new Sneakers(p)));
decorated.show();
}
}

class Finery extends Person {
protected Person caller;
public Finery(Person caller) {
this.caller = caller;
}

public void show() {
caller.show();
}
}

class TShirt extends Finery {
public TShirt(Person caller) {
super(caller);
}

public void show() {
System.out.print(“TShirt “);
caller.show();
}
}

class BigTrouser extends Finery {
public BigTrouser(Person caller) {
super(caller);
}

public void show() {
System.out.print(“BigTrouser “);
caller.show();
}
}

class Sneakers extends Finery {
public Sneakers(Person caller) {
super(caller);
}

public void show() {
System.out.print(“Sneakers “);
caller.show();
}
}

最后的主函数就是三句:

Person p = new Person(“Alice”);
Finery decorated = new TShirt(new BigTrouser(new Sneakers(p)));
decorated.show();

【典型例子】
Java IO里的:InputStream (base object),FilterInputStream(Decorator) 及其实
现:BufferedInputStream, DeflatorInputStream, InflaterInputStream,
LineNumberInputStream, PushbackInputStream etc.

FileInputStream, StringBufferInputStream, ByteArrayInputStream则是
InputStream的实现。

【附记】
Decorator和Chain of Responsibility的区别,后者是链上的某一个环节,把发来的请
求处理了,就返回;不处理,就扔给下一个环节。

【副作用】
* Decorator可能导致很多小class,每一个都只是功能的一部分实现。
* Decorator可以动态添加,该用多少,互相之间有什么影响,这些动态因素可能影响
将来的维护。尤其是如果Decorator里有任何的假设,或者dependency,将来很可能不经意地引入bug。

Read Full Post »

去年圣诞节,室友和女朋友约会去了,我一个人在房间里。

我把浴室的灯打开,把热水打开,浴室里就雾气腾腾的透出光亮,像一个神迹显现。

我在隔着一个客厅的房间里上网,发帖子,发微薄,假装自己正在等一个女人洗完澡,出来陪我**,但其实水是我开的,浴室里没人。

我的室友回来了,带着一个女孩儿,他很吃惊的看着浴室。

你带了人回来吗?

我应该诚实,但真相太可悲了,我说是的,我带了人回来。

他拍拍我,说好小子,真看不出来呀,那就不打扰你了。神色隐秘的一笑,和他的女友钻进他的房间了。

但其实浴室里没有人,水是我开的,过了一会儿,我觉得这样很浪费,就把水关了。回到自己的房间睡着了。

后来我的室友就跟人说,我有一个女友。别人就问我,你有一个女友吗?

我怎么说呢?我只能说是,我不能告诉他们水是我开的,卧室里没有人。

所以我度过了一段麻烦的日子。我不能再参与单身汉的下班活动了,因为他们听说,我有个女友。

去陪你的小情人吧,他们一群人哄着赶走了我。

公司里发电影票,他们给了我两张,我装作很感谢的样子,可是我从哪儿找另一个人陪我去看电影呢?

所以我一个人,旁边的位置上放着我的爆米花。

你和你的女朋友吵架吗?他们问我。我该怎么回答呢?我说,不经常吵。这是真的,我们没吵过架。

有些时候他们老见不到我的女朋友,就很奇怪,为什么见不到你的女朋友呢?而且有些心直口快的女孩儿说,你都从不给你女朋友买东西,还说,不吵架,就是快散了。

所以我被他们拉着买了一些女人的小玩意,有些东西真的不错,我想,在我送给她的时候,她会很高兴吧。

后来他们还说没有见过我的女朋友,一直都没有,我怎么办?告诉他们浴室里没有人,水是我开的?

我说不出口,没有办法,我买了一些卫生巾,不常见的型号,还有唇膏,一些粉底。

有人走进我的房间,说这些东西是谁的?

是我女朋友的,因为她有时候在我这里过夜,所以我为她准备了一些常用品,比如卫生巾,都是她特别用的那种。

女人听完泪眼婆娑,揪住她男友的袖子,你看看人家。连男人都露出了不好意思的神色。

谁会不信我呢?谁会不信我有个女友呢?

只是她性格怪癖,不爱见人而已。

我上班前在封底上揩一下,抹在脸上。

要是有一部相机留下每天我卧室内的照片,那些东西都在一天天减少,看起来就好像有个隐形的女友一样。

人人都相信我有个女朋友。没有人会发现浴室里其实没有人,水是我开的。

又过了很长时间。

我们老板找我去办公室,面带关切,莫名其妙的给我一天的假,隔壁桌的两个女孩儿面带同情的看着我,鼓励我,想我这么好的男人,肯定能找到更好的。

我才知道有人看到我一个人去看电影了,还看到我一个人坐了两个人的位子,在电影中间哭。

哦,原来我是失恋了。虽然那部片子很感人啊。

我简直想痛骂自己一顿,这是早就可以通往解脱的一条路,我早就该这么说啊。仅仅是因为卫生巾和粉底不算贵,我不知不觉过来好长时间。

我立即揪着自己的头发,很痛苦的样子,喃喃得看着远方。她们又捂着嘴,抽鼻子,背过头,有一个人忍不住哭了。

我没哭,我跟我女友没什么感情。

我又单身了,吃过两顿安慰饭之后,一切又回归了生活的平静。有女孩儿要给我介绍女朋友。

他为她女友买专用的卫生巾呢!她们不厌其烦的说,讲了很多我都不知道的痴情故事,是吗?那个介绍来的女孩偏过头来问我。

我怎么办,我只能说,是啊,难道要我告诉她,浴室里没有人,水是我开的?

我跟那个女孩儿出去了两次,后来她委婉的把这事儿分了。

你的心里空落落的 ,我感觉你还爱着她,我没信心取代这个位置。她眼红红的,临走还给我一拥抱。

这姑娘真够意思。

后来就没有人再介绍女孩儿给我了。

经她这么一点拨,我开始想念起我的前女友来,然后我想起来,我没有一个前女友啊。

浴室里没有人,水是我开的。

又到了一年圣诞节,还是那个室友,和一个不同的姑娘出去了,我一个人在屋里上网。

我那时候想,不知道那一次圣诞节,我究竟是因为什么把浴室里的热水打开呢?

一个人点着一根烟,在昏暗的灯光里,感觉很冷。想了很久,突然想起来,原来我是在想象有一个女孩儿是属于我的。

没有抗拒这诱惑力,我打开灯,扭开热水,浴室里就雾气腾腾透出光亮,像一个神迹显现。

这时候,我的室友抱着那个女孩回来了,他看着浴室,先是好奇,接着露出了惊讶和欣喜的神色。

是她回来了?

他旁边的女孩儿都跟着高兴和惊讶起来,是你跟我说的那个吗?是他以前那个女朋友吗?

两个人调子都变了,在客厅里高兴的又蹦又跳,好像约瑟和玛利亚。

不,我说。

浴室里没有人,水是我开的。

Read Full Post »

不解风情的死理性派们在情感生活中不免会遇到这样悲催的一幕:偶然间遇到一位心仪的漂亮女孩,从此日思夜想,废寝忘食,开始了漫长的暗恋之旅,等到一日,在无尽的纠结中,终于鼓起勇气向女孩表白,结果女孩一句“我已经有男朋友了”如晴天霹雳,实在难以接受……

为了避免此种尴尬的发生,如何准确判断一位女生是否单身就成了一项的必修的课程。

如果自己和女孩在一起共事,常常在她的身边,了解她是否单身就不是难题了。可是死理性派们要完成的高难度任务是:作为一个与女孩保持距离的陌生人, 在女孩毫无察觉的情况下,就可以用手头有限的信息判断出女孩的单身情况,不仅如此,死理性派追求的结果一定是量化的,计算出的mm单身概率还要保留两位小 数。

做法是这样的:第一步,要相信直觉。死理性派可以考虑多找几个朋友一起秘密观察一下目标女孩,当然找的人不要都是死理性派,什么心事鉴定组、谣言粉 碎机、自然控、犯罪法医谜最好都找几个来,已婚人士、情场老手、采花大盗也要找一下,人越多越好,越多样化越好。然后大家根据自己对mm的印象从各自的角 度估计一下目标mm单身的概率是多少,投一下票,最后的结果一定会有差异了,仁者见仁,智者见智,可能心事鉴定组觉着女孩单身可能性是90%,谣言粉碎娘 却觉着mm单身只是谣言。死理性派根据这些人平时一贯的靠谱程度,把每个人说出的概率平均一下,原则是平时比较靠谱的人给出的结果考虑的比重就要大一些, 不靠谱的人给的数字就要小一些,假设最后得到的结果是:该女孩单身概率为65.65%,我们就完成了第一步。

以上结果只是依靠各位投票者个人经验感性给出的结果,哪里符合死理性派客观理性的做事风格呢?为此,我们要进行第二步,要用事实和证据说话。一个mm是不是单身,可以从很多细节中寻找答案。

就像做科学研究一样,可以先查一下资料,google上随便一搜就可以找到不少寂寞人士多年潜心研究的简单易用的单身判别标准,比如手机原则(恋爱 中的女生手机使用频率会比较高),自习原则(单身的女孩常常和几个女生结伴上自习的话)。之后,在自己身边已经知道是否单身的女孩人群中做一下统计实验, 当然样本越大越好了,得到

诸如此类的统计值。

当这些“实验数据”都到手了,我们就可以继续了,对刚刚投票出来的概率值65.65%进行修正和优化。依靠的是什么呢? 自然是目标女孩在各项标准上的表现。比如发现目标mm喜欢和朋友一起去上自习,而根据自己的“统计研究结果”:在已经恋爱的mm中,喜欢和朋友一起去上自 习的女孩大约占其中的60%;在没有恋爱的女孩中,喜欢和朋友一起去上自习的女孩大约占其中的30%;

那么现在目标mm是单身的概率就会变为

死理性派心中一定暗暗高兴,希望增大了!

如果研究结果还发现,在单身女孩中,手机使用率大于1.2次/小时占其中的80%;在已经恋爱的mm中,这一数值则是40%;可是对于目标mm的观察结果是,她的手机使用率低于每小时1.2次,那么概率结果又要更新了。

这回mm单身的概率又悲剧地降回了56.02%,死理性派可以去找更多的“评核标准”,做更多的研究,不断更新女孩的单身概率值,让它越来越贴近事 实,不过在得到最后的结果之前,自己要先定一个阈值:女孩单身概率超过这个阈值(比如90%),自己才值得出手表白,否则,还是直接死心吧。

不过要注意的是,不管计算次数多少,得到的终归是一个概率值,不是事实,就算经过多次研究,已经可以将目标女孩的单身概率确定到99.9%,马上就 准备向她表白了,可是在最后一次对女孩的观察研究中,发现人家和一个男生手挽手的有说有笑,拥抱在一起,那么,女孩的单身概率值会立刻从99.9%掉到接 近于0,后果可想而知了……

本文告诉大家的这种判断mm是否单身的科学而严谨的死理性方法称为贝叶斯统计方法。贝叶斯方法简单的说就是“先验概率+新得到的证据=更正后的概 率”,可以不受信息量多少的限制,将各种来源的结果,包括主观判断和有限的客观信息,综合到一起,得到最后的结论。这里严正声明,本方法存在一定风险,尝 试时需谨慎,小朋友就不要尝试了。

不过话说回来,死理性派发明的贝叶斯大法还是不可小视的,美国海军在汪洋大海里搜索丢失的氢弹、失踪的核潜艇都用过这种方法,下面我们就由情感频道转台到历史频道。

1966年1月的一天,美国一架B-52轰炸机在西班牙的帕洛玛雷斯上空飞过,飞机上的几位飞行员在执行着空军司令部安排给他们的空中加油任务。按 理说这次飞行称不上危险,据说机长还是个很淡定的人,没事儿喜欢拿个大烟斗抽两口,甚至在飞机飞行舱里也不例外。可是这一会机长和他的几个部下可遇到大麻 烦了,以后能不能再享用大烟斗都难说了。在一次加油的时候,负责加油的运输机试图从其右后侧方接近B-52轰炸机,以便把柔性输油管送至对方飞机上。两机 速度没有控制好,互相撞擦了一下,这一“亲密接触”不要紧,加油机的油立刻起火爆炸,B-52也被撞的不轻,两架飞机的飞行员当场死的死,跳伞的跳伞……

可是故事还没有完,之后又发生一连串的悲剧和喜剧

为了找那一枚丢失的氢弹,美国赶紧从国内调集了包括了多位专家的搜索部队前往现场,其中也包括一位名为John Craven的数学家,头衔是“美国海军特别计划部首席科学家”,既然是特别的,那就不是一般的,Craven博士做的工作到底有什么特别之处呢?

【John Craven】

在寻找氢弹的问题上,Craven提出的方案使用了刚刚提到的贝叶斯方法,他召集了各方面的专家,不过每个专家都有自己擅长的领域,并非通才。有的 对于B-52轰炸机了解甚多,却对于氢弹的特性知之甚少。氢弹如何储存在飞机上是一个问题,氢弹怎么从飞机上掉下来又是一个问题;氢弹会不会和飞机残骸在 一起也没有答案;氢弹上的两个降落伞各自打开的概率是多少?风的流速和方向?氢弹落到地上之后会被埋到土里吗?

对于这些各式各样的问题,Craven要求专家们做出各种假设,想象出各种情景,然后在各种情境下猜测出氢弹在各个位置的概率,以及每种情境出现的可能性。

Craven的做法也受到了同行的质疑,因为在他的方案中,结果很多是这些专家以猜测、投票甚至可以说赌博的形式得到的,无法保证所有结果的准确性,可是因为搜索氢弹的任务紧迫,并没有时间进行精确的实验、建立完整可靠的理论,Craven的办法不失为一个可行的办法。

Craven得到了从专家那里“招供”的结果后,综合到一起,画了一张氢弹位置的概率图:把整个可能的区域划分成了很多个小方格,每一个小方格有不 同的概率值,有高有低,如同地图上表示山峰和山谷的等高线一样。像判断女孩是否单身的死理性派们一样,Craven完成了贝叶斯方法的第一步。

之后,Craven和搜索部队的指挥官一起开始了对氢弹的搜索,在搜索的过程中同时对每个格子的概率进行更新,不过,概率最大的方格子指示的位置常 常是陆地上险峻的峡谷和深海区,即使氢弹真的在那里,也未必找得到,所以需要绘制另一张概率图,表示“氢弹已经在那里,能找到的概率”而不是氢弹位置的概 率。最后氢弹被找到,Craven的两张概率图和他的贝叶斯方法发挥了不小作用。

仅仅过了两年,到1968年,Craven又有机会发挥一下才能了,上回丢了个小小的氢弹,这回美国海军丢了个“大个儿”的。

1968年6月海军的天蝎号核潜艇在大西洋亚速海海域一下子失踪了,潜艇和艇上的99名海军官兵全部杳无音信。按照事后调查报告说法,罪魁祸首是这艘潜艇上的一枚奇怪的鱼雷,发射出去后竟然敌我不分,扭头射向自己,让潜艇中弹爆炸。

为了寻找天蝎号的位置,美国海军进行了大规模的搜索,Craven自然也参与其中。由于失事时潜艇航行的速度快慢,方向,爆炸冲击力的大小方向,爆 炸时潜艇方向舵的指向都是未知量,即使知道潜艇在哪里爆炸,也很难确定潜艇残骸最后被海水冲到哪里。Craven初略地估计一下,在半径20英里的圆圈内 的海底,天蝎潜艇都有可能躺在那里,要在这么大的范围内、这么深的海底找到潜艇几乎成了不可能完成的任务。

没有专家能准确的估计到,在出事前后潜艇到底发生了什么,和搜索氢弹的时候一样,Craven咨询了数学家、潜艇专家、海事搜救各个领域的专家,编 写了各种可能的“剧本”,让他们按照自己的知识和经验对于情况会向哪一个方向发展进行猜测。据说,为了给枯燥的工作增加一些趣味,Craven还准备了威 士忌酒作为“投注”正确的奖品。

最后,Craven得到了一张20英里海域的概率图。整个海域被划分成了很多个小格子,每个小格子有两个概率值p和q,p是潜艇躺在这个格子里的概 率,q是如果潜艇在这个格子里,它被搜索到的概率。按照经验,第二个概率值主要跟海域的水深有关,在深海区域搜索时失事潜艇“漏网”的可能性会更大。如果 一个格子被搜索后,没有发现潜艇的踪迹,按照贝叶斯原理更新后,这个格子潜艇存在的概率就会降低:

其他各个格子的潜艇存在的概率值就会上升:

每次寻找时会挑选整个区域内潜艇存在概率值最高的一个格子进行搜索,如果没有发现,概率分布图会被“洗牌”一次,搜寻船只就会驶向新的“最可疑格子”进行搜索,这样一直下去,直到找到天蝎为止。

最初的时候,海军人员凭经验估计潜艇是在爆炸点的东侧海底,对于Craven和其它数学家的建议嗤之以鼻,但是几个月的搜索后一无所获。后来海军不 得不听从了Craven的建议,按照概率图,失事后的潜艇应该在爆炸点的西侧。经过几次搜索,潜艇果然在爆炸点西南方的海底被找到了。

经过两次给力的表现,Craven在海事搜索中使用的贝叶斯方法逐渐被广为接受,从此,贝叶斯方法意想不到地常常和氢弹、核潜艇一起成为关键词在各 处出现。几十年间,贝叶斯方法应用越来越广泛,从google搜索筛选词条到无人驾驶汽车综合判断自己的行驶位置,钻进了各个角落。当然,这个神奇的方法 用在追女上实在是大材小用了。

(本文获得2011年台湾财团法人国立自然科学博物馆文教基金会第五届人与自然科普写作桂冠奖征文竞赛第三名。)

Read Full Post »