Posts Tagged ‘ state_machine

UNO牌Ruby源码实在是写得太烂了

最近研究UNO牌,想自己写一个,于是google一下,准备参考。还真的发现有人用Ruby写了一个,真不错啊。
http://www.merkisoft.ch/projects/uno/

由于估计是瑞士人写的,所以文档和程序的命名都是用的德语,看着好吃力,幸亏有google在线翻译,我把大部分文字都翻译成了英文。这是我第一次近距离接触德语,感觉怕怕的,语法甚是我辈所不能理解。

看着看着,我就惊讶了。这是什么烂程序啊。我一向以为欧洲程序员有严谨的作风,受过良好教育,守规矩,有各种好的习惯。看来并不是所有人都像DHH一样啦。我把一些心得体会记下来,警醒自己和同路中人今后不要走弯路。由于源代码只能看不能摸,哦,是不能发布,所以这里我不能贴出源码片段。

1. 有一个函数将近100行,它的名字叫做run。我在豆瓣上说我看见了一个100行的ruby函数,众人立刻对我表示深深的同情。有人说20行就已经很多了。看来今天我确实遇到极品了。那么这么重的坏味道究竟来自于哪里呢,哦原来作者用if实现了一个有限状态机(FSM),多么极品的想法啊。长的函数是代码坏味道之首,一定要重写。有限状态机的相关部分可以参考state_machine不要重复发明轮子DRY,更不要用Ruby重复发明轮子,我真的很在意。另外说一句state_machine的作者也算呕心沥血,花了多年时间才到这个程度的,大家应该能感觉到其功力深厚。

2. 没有大胆使用class。换句话说没有有效的封装。最典型的例子就是“牌”,UNO牌是一种桌面游戏,其基本元素就是一张张的牌。作者似乎定义了一些类,但是却没有一个是关于“牌”的。这就导致了在其他类的内部出现了大量的不相关的代码。没有关于“牌”的类,自然关于牌的函数也是凤毛麟角,这样对系统的主要逻辑产生了很大冲击。不易于看到程序清晰的主干。建议做一个Card类,将牌本身的一些动作封装进去。

3. 使用魔法数。靠数字辨别颜色,靠数字表明牌的类型。这个坏处就不用多讲了。别让大家需要猜谜语,于人于己都不是好主意。应该更多的使用常量和符号,杜绝使用魔法数。

4. 将一个功能分散到多个文件中实现。我承认的确有些复杂的功能需要把功能实现分散开。但是这里的情况完全不是这样的。本身一些定义很清晰应该在模型一层。其中有一个类的实例方法在三个不同的地方实现。问题是文件之间应该确保调用顺序,要在文件当中写明require。但事实上这项工作交给了调用者,也就是说调用者得到功能与其require加载的顺序有关,一旦加载顺序不对,程序就完蛋了。这样的东西纯属人祸,我相信有很多不明不白的程序就是这么完蛋的。建议在一个文件中实现相关功能,文件头部必须显式调用所依赖的库或文件。

5. 莫名其妙的命名,比如ok。

6. 自己发明的单元测试,美其名曰单元测试。建议使用标准的单元测试框架,或者使用Rspec这样的奇妙工具。测试的力度还应该加大一些,目前来看还远不到单元的级别。

其他一些想法

1. 设计上通讯应该和模型逻辑等分离。模型接口应该和通讯高度解耦,这样就可以用任意通讯程序替换,这也是提高可测性的考虑。

2. 命名要遵守一般原则。比如用UNO的标准用语来命名牌或者动作,而不要使用同义词。最好使用英语,其他语言的命名还是不提倡。

3. 使用reek这样的工具,保持警惕性,保持对坏味道敏感性。看看结果

mars@mars-laptop:~/uno-ruby/src$ reek . | grep "LongMethod"
  AdvancedSpieler#karteWaehlen has approx 15 statements (LongMethod)
  Mensch#karteWaehlen has approx 21 statements (LongMethod)
  Mensch#zeigeKarten has approx 9 statements (LongMethod)
  busy has approx 6 statements (LongMethod)
  Spiel#add has approx 7 statements (LongMethod)
  Spiel#run has approx 37 statements (LongMethod)

要想保持完美体型就得经常锻炼,而且得科学锻炼,写程序也是一样,要讲求科学,要不断反省自己,另外要经常写,保持肌肉健壮。