OOBootcamp,全称是Object-oriented Bootcamp,就是面向对象训练营的意思,在12月份和1月份,我们整个项目组一些senior一些的同事一起接受了OOBootcamp的培训,有这个培训呢,还多亏了训杰找骏总争取到了这个机会,Tech leader做得好,关心组员的技术成长,哈哈。讲师是袁慎建老师,intellij 的快捷键玩的出神入化,技术实力没得说,又会说话还又会写文章,关键是人还长得特别帅,这就让人有点嫉妒了,附上他的地址,欢迎大家前去骚扰.
前些天这次OOBootcamp圆满结束,完结撒花。领到了一张小奖状
当然还有一些附带的训战计划,是需要在实际项目中去落实所学到的知识。所以之后还会有一些在实际项目中实践这次学到的知识之后的感悟,好,废话不多说了,下面就来回顾总结一下这次的OOBootcamp.
这次OOBootcamp, 主要分了三大块的内容,一个是SOLID原则, Simple Design原则,贫血充血模型,二是TDD, 三是重构,主要用了Parking Lot 这个编程实例来给我们做练习,学以致用,先用例子实现一遍,再之后在真实项目中再操练一遍,几板斧下来,保准你映像深刻,又有自己的体悟。
SOLID 原则 (面向对象的基石)
SOLID原则,我自己就不多现丑了,附上一篇袁老师的博文,供大家参考,虽然主要讲的是里氏替换原则,但是其他原则附带也有讲,网上一搜索也是一大把。我下面就讲一下我在OOBootcamp结束后对SOLID原则的理解,就不会讲很多细节了。
先复制一波袁老师的一段文字
SOLID由五大原则构成:
- Single Responsibility Principle【单一职责原则】
- Open Close Principle【开闭原则】
- Liskov Substitution Principle【里氏替换原则】
- Interface Segregation Principle【接口隔离原则】
- Dependency Inversion Principle【依赖倒置原则】
并且请记住一句话,所有的原则其实都是为开闭原则服务的
注:袁老师说,你们知道了这几个原则后,就把它忘掉,不过,我可能还没到这种无招胜有招的境界,所以需要详细记录一下
1. 单一职责原则
我理解的单一职责原则很简单, 就是一个类,或者一个方法,它就只做一件事情,当然这句话说了等于没说,你可能会说我知道单一职责是什么意思,但还是不会啊。对,我也不会,没人敢说他会。因为单一职责的难点就在于这个一件事,到底是怎么个一件事,或者说怎么分。大到组装一台车,小到造一个螺丝钉,它都是一件事。怎么分?
其实这是一个很难的问题,这个问题是没有一个标准的答案的,它跟你的业务相关,跟你具体的实践相关。不是一个精准的度量而是一个感官度量。唯有以面向对象的方式多多练习,才能找到那个感觉。以我的感受来说,当你觉得你找出的那一件事情,分无可分,不存在二义性,那么我认为这就是单一职责的一件事。
2. 开闭原则
开闭原则的定义是对扩展开放,对修改关闭。这个真的咋一看云里雾里,要理解这个原则,我觉得我们首先得把这句话的作用域缩小一点,就针对java 面向对象来说,其实应该说的是,扩展功能可以增加新的类,但应该尽量避免修改已有的类。其实还是有点不好理解哈,也不知道怎么做,我觉得这时候可以去看下设计模式,全部的设计模式都遵循了开闭原则,简直是最好的开闭原则示范。
3. 里氏替换原则
墙裂推荐袁老师的这篇博文 让里氏替换原则为你效力,写得很好,简直写到我心坎里去了。
我这里就只说说里氏替换原则的定义,任何基类可以出现的地方,子类一定可以出现,如果你覆写了父类的方法,那么其实你是违背里氏替换原则的。至于为什么,都在袁老师的博文里,欢迎大家去围观,哈哈。
4. 接口隔离原则
接口隔离原则的定义是 客户端不应该依赖它不需要的接口;类间的依赖关系应该建立在最小的接口上
这个原则,看定义就很好理解了,其实我认为它是对单一职责的一种补充,因为如果你单一职责做的非常好,那么你其实会很少碰到违背接口隔离的情况的,反之,如果真的出现了,那么一定是你单一职责做的还是不够好。
5. 依赖倒置原则
这个原则的定义 是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了耦合
这个其实也是有点绕的,面向抽象编程怎么就降低了耦合呢?其实我觉得还是很好理解的,比如你吃一个苹果,如果写实现,你写一个苹果类就完事儿了,但是如果面向抽象你需要定义一个苹果接口,然后我吃这个接口就好,不关心是什么苹果,然后再写苹果的实现,但是后面还有需求,说要吃一个美国的苹果,因为美国苹果又大又圆,面向实现的做法是,我改一下我的那个苹果类改成美国苹果就完了,面向抽象的做法是,我在写一个类,美国苹果类实现苹果接口,其实对比两种方式,你就能知道谁好谁坏。
Simple Design(简单设计)
简单设计原则 我还是推荐大家去读袁老师的文章 简单设计落地三板斧 其中链接了 简单设计的价值观和 简单设计原则,哈哈,都是他写的,十分高产。
简单设计原则在我看来核心就是 保持简单。对于程序开发来说,就是保持程序简单,不要去过度设计,我们写程序的时候常常会不由自主的为未来去考虑,比如一个好的程序员,他会有这样个习惯,他不会单单只完成自己手上的工作,他会去思考未来会有什么需求进来,于是这些思考会常常体现在代码上,形成过度设计。而如果你认同敏捷价值观,那么这其实并不是一个好的习惯,因为你思考的需求,其实有时候并不会实际产生,于是代码凭空复杂了很多,并且有一部分还是无用代码。
Simple Design 就是需要你去认同这样的价值观 保持程序简单,不要过度设计,你的程序实现应该是刚刚好满足你现有的需求的。
那么怎么去落地Simple Design呢?请看袁老师讲的Simple Design落地三板斧。TDD, 重构,clean code, 哈哈
培训的时候映像比较深的是这样一幅图
这个是说的在实践简单设计过程中,当面临冲突时,我们如何取舍,最重要的是通过所有测试,其次是消除重复和解释意图,优先级最低的是减少元素,当有冲突时,优先级越高的我们越应该关注。
贫血充血模型
期间由训杰给我们分享了贫血和充血模型,现在一般Java 项目多数都会采用spring全家桶,而spring所推崇的mvc结构是一种典型的贫血模型,于是迅杰给我们分享了贫血模型和充血模型的区别(好处和坏处),以及充血模型的架构应该是怎么样的
不了解贫血模型和充血模型的, 可以看看这篇文章 贫血模型与充血模型的对比
以下是摘抄自上面那篇文章
贫血模型的好处是:
1、每个贫血对象职责单一,所以模块解藕程度很高,有利于错误的隔离。
2、非常重要的是,这种模型非常适合于软件外包和大规模软件团队的协作。每个编程个体只需要负责单一职责的小对象模块编写,不会互相影响。
贫血模型的坏处是:
1、由于对象状态和行为分离,所以一个完整的业务逻辑的描述不能够在一个类当中完成,而是一组互相协作的类共同完成的。因此可复用的颗粒度比较 小,代码量膨胀的很厉害,最重要的是业务逻辑的描述能力比较差,一个稍微复杂的业务逻辑,就需要太多类和太多代码去表达(针对我们假定的这个简单的工时管 理系统的业务逻辑实现,ruby使用了50行代码,但Java至少要上千行代码)。
2、对象协作依赖于外部容器的组装,因此裸写代码是不可能的了,必须借助于外部的IoC容器。
对于Ruby来说,更加适合充血模型。因为ruby语言的表达能力非常强大,现在用ruby做企业应用的DSL是一个很热门的领域,DSL说白了就是用来描述某个行业业务逻辑的专用语言。
充血模型的好处是:
1、对象自洽程度很高,表达能力很强,因此非常适合于复杂的企业业务逻辑的实现,以及可复用程度比较高。
2、不必依赖外部容器的组装,所以RoR没有IoC的概念。
充血模型的坏处是:
1、对象高度自洽的结果是不利于大规模团队分工协作。一个编程个体至少要完成一个完整业务逻辑的功能。对于单个完整业务逻辑,无法再细分下去了。
2、随着业务逻辑的变动,领域模型可能会处于比较频繁的变动状态中,领域模型不够稳定也会带来web层代码频繁变动。
TDD
上面的理论讲完了之后,就是实践了,用TDD 这个工具来实现一个比较经典的题目ParkingLot Management,在回顾TDD之前我要先回顾一下Tasking 思维管理工具
Tasking
Tasking 是一种思维管理工具, 它要求你对于一个比较复杂的问题,列出它的每一个完整子路径,这样进行Task分解之后,复杂的问题就变成了一个个简单的容易实现的子问题了
Tasking 的经典格式是 Given When Then 格式,一个典型停车小弟的需求Tasking分解的例子是
Given: 我管理一个停车场,停车场有空位
When: 用户委托我停一辆车
Then: 停车成功
可以看到,Tasking要求你分解出的Task要尽量的简单并且可测试
一个错误的例子是
Given: 我管理N个停车场,停车场N个空位
When: 用户委托我停N辆车
Then: 停车成功
这个Task其实是很难去测试的,N是多少?测试里面你难道要穷举吗?怎么穷举?
TDD
TDD 测试驱动开发,是极限编程列出的12个团队实践之一,是其中重要的组成部分,TDD的具体理论和一些实践,在网上都有,我这里只讲我练写TDD的感受,首先 TDD 是一个工具,是工具就有适用场景的,也就是说,不是所有的软件开发过程都适合使用TDD,其次它是由三个单词组成
- Task Driven Development
- Test Driven Development
- Test Driven Design
三个词中都出现了Driven,所以,TDD的核心其实是那三个D, 而且从2,3两个词可以看出,TDD是必须Test First的
其实在课程开始之前,我对TDD的理解很粗浅,仅仅限于先写测试后写实现(当然,其实这样理解也没什么毛病),在实践中也几乎没有去实践,我们总是习惯于先写实现,写完之后再回过头来补测试. 其实有时候就会有一种想法,觉得测试是一个可有可无的东西,你写了更好,没写程序也不会出啥大的问题。就像是客户要求我们测试覆盖率90%以上,写测试就是为了完成客户的需求。
但是,TDD要求你重视测试,测试是一等公民, 你得先写测试再去写实现,你的测试得能表现你的业务,让别人一看你的测试,就知道你的业务是啥,这就倒了过来,变成了实现是小儿子,哈哈
因为在TDD看来,好的能完整表现业务的测试,至少不会让你漏了边边角角,而且这样写下来,理想情况下,测试的覆盖率应该是百分之一百的. 你会对你写的代码无比有信心,因为在需求范围内,你可以宣称你写的代码是百分之百的满足需求。
课程开始的时候,我们对TDD要求的 Test First是什么,是很明确的,就是先写测试后写实现,Task Driven Development 这个也是比较好理解的,但是后两个确难到我们了,测试怎么去驱动开发,怎么去驱动你的设计,你的设计是测试驱动出来的吗?以至于我们花了两节课的时间去讨论这些问题。下面我附上,我们讨论的结论
- 我们写测试之前,脑海里是有提前设计的,没有提前设计,你测试都写不了更不用说写实现。
- 测试并不能驱动你的设计,只会让你发现坏的设计(坏的设计测试很难写)
下面是我们TDD的实践步骤:
跟客户确认当前阶段需求,确认到什么程度呢?你觉得你能开始列Task了的时候,之后随时保持与客户的紧密沟通,需求有任何问题找客户沟通
选择视角(就是选择看待问题的视角,比如是用户视角还是停车场管理员视角),运用Tasking思维管理工具 列Task,Task是以业务场景为单位的,每一个Task都是要有交付价值,何为交付价值,就是客户拿来能用的功能(能跑起来的程序), 代码写了一半,跑都跑不起来的,没有任何价值. 先列简单场景的Task 后列复杂场景的.
-
根据Task列表,从前到后一个一个的实现,由于我们是采取Pair的编程方式,所以是一个人写测试,然后另一人写实现,之后轮换
提前设计注意点:- 写Task之前,是要有提前设计的
- 提前设计必须根据你当前已经做完和正要做的Task做提前设计,禁止根据全部的Task做提前设计
写测试的注意点: - 测试必须能表现你当前Task的业务
- 抽象关系可以体现在测试里
写实现的注意点: - 实现的代码是基于你的提前设计的 (不要做重复的无用功)
- 实现的代码要刚刚好满足你的业务,何为刚刚好?就是删除任意一行你的代码,你的测试就会挂掉, 反之,就不是刚刚好
- 看到不爽的代码就重构,什么是不爽的代码?请看 clean code 这本书
全部Task完成之后就是向客户交付产品了,由于我们的客户是袁老师,所以给他review了代码,保证所有的测试是绿的
TDD 这个东西,如果你单看字面,不去实践,是无法领会它的精髓的,我觉得初学者如果要去实践TDD, 可以不用去考虑它的后两个单词,只看Test First,先实践一下Test First, 然后再慢慢去加上后两个Driven,最终你会达到那种得心应手的境界,当然还可能是放弃,哈哈
据说如果熟练使用了TDD, 开发效率会比不使用TDD提升很多,这个我暂时也还没体会到,看来是实践的少了,之后会找机会多实践TDD
重构
重构我就不多介绍了(要介绍也只能贴贴各种代码示例),请大家移步 重构 这本书
我就只说说我的感受
- 当你看到不爽的点的时候,你就要重构你的代码
- 不爽的点应该是违背团队的代码约定的,而不是违背你自己的小癖好
- 最好通读一遍 clean code, 这样你说别人代码不好的时候也不至于词穷(可以指着书本骂他)
- 光看重构这本书,其实没多大用处,你看过就会忘,你必须结合它来一场说写就写的实践, clean code也一样
- clean code, 重构这两本书,请常备在桌面上,说不准啥时候就会用到,无论是指着书本骂人,还是适当的装一下逼
总结
OOBootcamp 培训给我的进步还是很大的,以前我虽说是用着面向对象的语言,确写着云里雾里,而这次培训下来,结合理论和实践,我深刻理解了面相对象的基本原则,简单设计原则,以及Tasking TDD等做法,而且也使我对敏捷有了更深刻的认知,确实是犹如醍醐灌顶,受用无穷。我相信这些知识最终也能带着我飞得更高。