代码整洁之道之读书笔记(第一遍阅读)

代码整洁之道之读书笔记(第一遍阅读)

我觉得这个整洁这个词用的特别好,就好像原本乱糟糟的房间里,衣服被整齐的放在衣柜里,垃圾被放在垃圾桶里,书放在书桌上,整个房间的样子第一眼就能看出来就是一个大的设计上的整洁,然后打开衣柜,打开书桌,里面是整洁的,就是具体的每个类和方法的整洁,最终变得赏心悦目。以下是一些阅读心得:


可读性:

好的代码应该像是一本书,打开它不需要任何外物就让人能够读懂,所以可读性是很重要的,比如说一个方法,它能够自己就表现出合理的可读性,让人知道这个方法是干啥的。而光是可读性这里就牵扯出很多东西。

1.命名规范:可读性的第一点就在于合理的命名规范,两个方法,一个叫calculateDistance,另一个叫function1,一看就可知道,第一个的命名更加舒服,便于理解,然后这里有一些约定俗成的命名规范,我觉得这种规范是一种对我们的保护,帮助我们形成惯性思维,看到这个命名就能理解它大概是什么意思,这样在阅读别人代码的时候即便还没有进入到方法或者类里面,就已经心里有了一个大概的轮廓。

这里的规范有很多,我大概试着举几个例子,第一点,要名副其实,是什么就叫什么,不要起意思相悖或者无关的名字;第二点,做有意义的区分,比如说一个swap函数,把临时变量命名为a1 a2就远不如source和destination,同时要避免废话,比如说moneyCount其实就是money,没有必要写这种废话;第三点:类名应该是名词,而方法名应该是动词,这个也符合我们对于他们的认知。第四点:给每个概念对应一个词,比如说获取,可能有get,fetch等等,这里要使用一个统一的名称,避免错乱。等等还有很多这种起名字的规范,日常使用的时候再慢慢实践和理解。

2.写好一个基本的方法:

方法或者函数,他们的第一原则就是要短小,如果超过了100行的方法,基本上都是要重构的,最好的是4到5行代码,看起来非常清晰。那如何做到短小,答案是,职责单一,是的,单一职责原则即便在方法层面上也是适用的,一个方法只做一件事,那它的内容自然精简,多出来的让别的方法去实现就好了,我个人觉得这一点也是保证可读性的最重要的原则,我记得有一个经典的小品,说把大象放进冰箱里需要几步,然后答案是三步,打开冰箱门,把大象塞进去,关门,所以就只要三步,我个人理解这和方法的单一职责也是一样的,这个方法可以是

Public void putElephantIntoBridge(Elephant elephant){

         openDoor();

         putElephant(elephant);

         closeDoor();

}

这样读起来是不是就会非常轻松,至于后续的,这个输入参数可以是个接口,比如说food,大象是它的实现类等等,就是别的事情了。


这里面有一个基本的原则在于,方法里的事情必须是在同一个抽象等级内的,这个很难界定,我自己也经常搞不清楚,未来实践中多理解吧。

有一些小技巧,比如说参数,函数的参数最好是0-1个,两个都显得太多,而三个以上就要开始考虑是否封装为对象了,比如说重复,经常重复的东西可以抽象出来作为一个新的方法,来避免重复。

3.注释:

以前我对于注释是觉得,应该把重要的地方注释一下,然后不重要的就不管了,但是现在发现不是这样的,这本书里特别强调的是,代码自身的解释性,就像是之前提到的那样,代码自己会解释自己,不需要注释,如果说你的代码不能解释自己,那第一反应应该不是注释,而是修改代码了,而注释真正需要使用的是哪些地方呢,比如说,警告或者对意图的解释,警告别人这里不能这么用或者是别的,todo,表示自己未来可能要做的事情。注意,注释并不能美化糟糕的代码,而且注释很难维护,程序员很少想起来在改代码的时候也去把注释改掉,这样反而会造成很多误解。


可扩展性    

说道可扩展性,第一反应就是六大设计原则了,这个基本上是面向对象的总纲的东西了,这个东西应该说,过一段时间理解的更深入一些,但是迄今为止,仍然只是懂了大意,可能还是需要更多的实践来体会。

首先是迪米特法则:它认为一个类C中的方法f,它所交互的对象调用的方法的对象只包括:


C自己

自己所创建的变量和实例(错误)[if !supportAnnotations][v1][endif] 

f自己所创建的变量和实例

作为参数传递给f的对象

C自己所持有的变量和实例

其实说白了,就是它只跟自己的朋友玩,不跟陌生人玩,而自己的朋友,只有自己手够得着的才算自己的朋友。

这个原则还有另一条解释,叫做对象对于自己所依赖的对象保留最小的认知。这个就很容易理解了,这种最小的认知,就可以保证依赖的对象在修改时,自己能够最大可能的不受到干扰。同样的,作为被依赖的对象也是,暴露出去的东西越少,自己受到的威胁也就越小。这也是为什么要进行封装的原因。

http://www.jianshu.com/p/14589fb6978e网上有一篇文章我觉得写的不错,它就是分了两个角度,依赖者和被依赖者,依赖者尽量只和自己相关的对象交互,被依赖者尽量不暴露太多的东西。这样双方都能最大程度的解耦,保证了系统的灵活。这一点我自己最近也正好有用到,

客户端访问接口/engine.do通过上传的参数(imei,model)来获取自己匹配到的engine,然后首先我会定义一个

 Publicclass engineController{

Private EngineMatchService engineMatchService;

Public ListgetEngine(Parameter parameter){

engineMatchService.getMatchedEngine(parameter);

}

}

而在engineMatchService中又会维护一个接口engineListService用来保存所有的搜索引擎的列表,这样把引擎列表本身和引擎匹配方案拆分成两个,业务调用方只需要调用引擎匹配方案即可,而无需关注引擎列表本身的变化。这种面向接口编程的思维现在也慢慢开始体会到了他的好处。



其次是里氏替换原则,这个原则的意思是说子类对象能够替代父类的,而程序逻辑不用变化。网上有这么一段话我觉得启发挺大的:


LSP的原定义比较复杂,我们一般对里氏替换原则 LSP的解释为:子类对象能够替换父类对象,而程序逻辑不变。

里氏替换原则有至少以下两种含义:

1. 里氏替换原则是针对继承而言的,如果继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义。子类只能通过新添加方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,当然逻辑一致,相安无事。


2. 如果继承的目的是为了多态,而多态的前提就是子类覆盖并重新定义父类的方法,为了符合LSP,我们应该将父类定义为抽象类,并定义抽象方法,让子类重新定义这些方法,当父类是抽象类时,父类就是不能实例化,所以也不存在可实例化的父类对象在程序里。也就不存在子类替换父类实例时逻辑不一致的可能。

不符合LSP的最常见的情况是,父类和子类都是可实例化的非抽象类,且父类的方法被子类重新定义,这一类的实现继承会造成父类和子类间的强耦合,也就是实际上并不相关的属性和方法牵强附会在一起,不利于程序扩展和维护。

如何符合LSP?总结一句话—— 就是尽量不要从可实例化的父类中继承,而是要使用基于抽象类和接口的继承。


那为什么要有这么一条规则,一点点来看,首先对于父类已经实例化的方法,我们一定是要尽量避免重写的,因为我们在实际的代码编写中,经常出现的一种引用方式就是父类的声明和子类的实现,然后调用的是父类的方法,那么一旦重写了父类方法,很容易引发错误和问题,另一方面,对于父类的抽象类和接口,这个原本就是需要子类去自行实现,允许各有不同的。

从面向对象的角度上来思考,父类或者说基类,它的已经实例化的方法就是会被认为是一些通用的无需更改的东西,这在上层设计时就要确定好,然后它所预留出来的接口和抽象方法,就是认为子类各有不同,自然需要子类去重写,接口在设计时也就考虑到了这一点。

你可能感兴趣的:(代码整洁之道之读书笔记(第一遍阅读))