《Python软件架构》读后随笔
代码的评价标准
什么是好代码?坏代码?可以从哪些维度评价?
-
可维护性
易维护的代码:能在不修改原有代码设计、不引入新bug的情况下修改或添加代码。
-
可测试性
-
可扩展性
-
可读性
-
可复用性
减少重复代码的编写,尽量复用已有代码。
面向对象中继承、多态的目的之一就是提高代码的可复用性。
很多设计模式所要达到的最终效果就是可复用性。
-
简洁性
KISS原则:Keep It Simple、Stupid
以上评价指标往往不是独立的评价维度,而是会互相影响,如可读性好、可扩展性好的代码相对可维护性强。但有时候为了提高代码的可复用性、可扩展性,对代码进行多种层次的抽象可能会降低代码的可读性。
编程的范式
如何写出高质量代码?掌握细化、能落地的方法论
-
面向对象设计思想
-
设计原则
-
设计模式
-
编码规范
-
重构技巧
关于编程方法论的一些问题
-
如何理解面向过程、面向对象、函数式编程这几种编程思想
-
面向对象编程、设计原则、设计模式、编程规范和代码重构的关系
-
面向过程编程和面向对象编程的区别和联系
-
接口和抽象类的区别以及各自的应用场景
-
组合和继承孰优孰劣?
-
如何理解基于接口而非实现编程?
Q:如何理解面向过程、面向对象、函数式编程这几种编程思想
面向过程编程
面向过程编程的主要特征是:将数据与操作数据的方法分离,以函数或子程序作为基本模块,通过将这些基本模块进行调用组合完成功能,简而言之就是流程化。
面向对象编程
提出了一种编程风格/范式,建议以“类”或“对象”构建代码的基本单元,以封装、继承、抽象、多态四个特性作为代码设计的基石。目前,大部分语言都是面向对象编程的语言(支持类和对象、以及各种面向对象特性),如Java、C++、Go、Python、Scala、PHP等。
Tips: Smalltalk是世界上第一个面向对象编程语言。
那么为什么要将“类”、“对象”作为代码的基本单元呢?这其实是一种归类的思想,编程本质上可以认为是对问题进行建模并得出程序化的工具,如果我们能将一组行为相似的实体归为一类,这一类实体具有相似的属性或行为,那么在处理这些实体时就(大概率)可以用同样的代码完成实现逻辑,所以基于这种设计思想写出的代码会具有较高的可复用性,因为它抽象出了同一类不同实体的属性、行为的共同点,于是可通过同一套逻辑(或者相差不大)实现对具有这些共同点的实体/对象的处理。
考虑到不同类之间既具有共同点又具有差异性的情况,人们提出了“继承”的概念,某一个类可以通过“继承”另一个类以复用被继承者(父类)的代码,同时也可以通过重写实现跟父类不同的行为。(相当于子承父业,但可以青出于蓝胜于蓝)。
关于继承的争议
随着实践经验的增多、工程师们发现继承在一些场合可能存在问题,于是开始反思继承本身存在的必要性以及可替代性。现代编程语言Go就摒弃了“继承”这一代码设计思想,提倡“组合优于继承”。
关于面向对象分析、面向对象设计、面向对象编程的区别
做什么(程序被拆分成哪些类、类有哪些属性)、怎么做(类与类之间怎么交互)、做(写代码,将面向对象的设计进行落地)。
Q:面向对象编程、设计原则、设计模式、编程规范和代码重构的关系
面向对象编程是一种编程思想,核心特性包括封装、继承、多态,核心理念是“抽象”,主要解决代码的可复用性问题。基于这种编程思想可以实现很多复杂的设计模式。
设计原则是指导代码设计的经验总结。
设计模式是有经验的工程师针对特定代码设计问题总结出的经验性解决方案或设计思路,主要解决代码的可扩展性问题;设计模式比设计原则更具体,设计原则比设计模式更抽象。(或者说,面向对象和设计原则偏向内功心法「如九阳真经、易筋经」、设计模式是具体招式「如降龙十八掌」、编程规范是基本武术动作,代码重构是在动作“变形”后的纠编操作)
编程规范是关于代码编写细节的可落地的具体规范,主要解决代码可读性的问题。
没有强大的内功基础,无法练就降龙十八掌;若只会内功不通招式,难以克敌制胜。
Q:面向过程、面向对象的区别和联系
面向过程核心思想:数据与方法分离,顺序式的执行完成业务逻辑;
使用面向对象语言写出来的代码就是面向对象的吗?有哪些似是而非的“面向对象”写法?
静态属性、静态方法、单例类:实际上只是将数据和方法(对数据的操作)进行分离,并没有实现面向对象级别的封装,因为这类属性、方法是全局可访问的,并没有“隐藏“某些细节。而面向对象级别的封装往往是隐藏了对业务不重要的属性和方法,只暴露必要的接口,从而减少代码误用的可能性。
面向对象编程一定比面向过程编程更优越吗?我们需要排斥面向过程的代码编写风格吗?
面向对象编程相比于面向过程编程风格的主要优势是复杂项目中可维护性、可拓展性、易读性较好,而简单场景下,面向过程可能会由于简单直接而更具有优越性,比如实现一个简单的算法、数据处理流程直接采用面向过程的编程风格更为直接简单,这种情况下即便使用面向对象风格得到的类及类间关系也十分单薄,可读性、可维护性方面不会有明显增益,而且代码会显得冗长。
Q:组合和继承孰优孰劣?
现代编程有一条实践原则是:多用“组合”少用继承,这条原则所说的组合与面向对象中的“聚合与组合“中的”组合“不同,后者的组合关系要求组分的生命周期依赖于整体的生命周期,而前者没有那么严格(或者说描述粒度较粗),也就是说”聚合“和”组合“统称为”组合“,只要一个类成为了另一个类的成员对象,就认为是一种组合关系。
那么为什么推荐多用“组合”少用继承呢?继承的问题在哪?
先回顾一下继承的优点,一开始设计继承主要是为了表达类之间的is-a关系,同时提高代码的可复用性,其中代码可复用性体现在两个维度:
-
子类如果不重写父类的方法就可以直接继承父类的方法,而不用重新实现一份同样的代码,即子类复用所继承父类的代码
-
多个子类的同一方法具有不同的实现,多态允许我们在不改变上游流程代码的前提下,根据不同的子类类型表现出不同的行为,从而复用上游流程代码。
继承的问题及组合的优点
尽管继承能带来上述关于可复用性的优点,但同时也引入了更多问题:
-
代码耦合性与可维护性问题:由于子类会继承父类的方法,那么父类中的任何修改都可能影响子类,当继承层次过深、代码由多人协作开发时就可能导致开发协作不那么顺畅/独立。且继承层次较深时也带来了更多复杂性:要求开发者在查看一个类时需要同时去了解类的继承关系,否则难以清楚一个类具有哪些行为。相比之下组合的代码关系更为直接且清晰。
-
灵活性较差:继承是类之间的层次结构关系,在编译时确定;而组合是类与对象的关系,运行时可以动态修改,因此相比之下继承的灵活性更差。
如果继承层次较少,是否使用继承会比使用组合更好?
继承的主要问题是继承层次较深、继承结构复杂时带来的代码结构不清晰、可维护性差、耦合性强问题,如果继承层次较少,则不存在上述问题,如果类之间确实是is-a关系,使用继承还能提高代码的可读性。
设计原则
-
设计原则的初衷是?
-
有哪些常用设计原则?其提出的设计意图是?
-
什么是SOLID?
SOLID
-
单一职责:Single Responsibility Principle
-
开闭原则:Open Close Principle
-
里氏替换:Liskov Substitution Principle
-
接口隔离:Interface Segregation Principle
-
依赖反转:Dependence Reverse
里氏替换(LSP)原则
If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program.
Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it.
接口设计的原则
-
单一职责
一个接口只负责完成一个职责/功能。判断职责是否单一,需要结合业务场景。
-
接口隔离原则
客户端(接口的调用者)不应该被强迫依赖它不需要的接口。
单一职责原则和接口隔离原则对比
单一职责原则:针对类、模块、接口设计
接口隔离原则:指导性侧重于接口设计,提供了一种判断接口是否符合单一职责的方式:通过调用者如何使用接口判断,如果调用者只使用了接口的部分功能,那么接口的职责就不够单一。
通用框架开发的指导原则
我们希望框架具有的特性
-
易用性
框架是否易集成、易插拔、跟业务代码是否松耦合、提供的接口是否够灵活
-
性能
框架本身的代码执行效率,对业务系统是否会有太多性能上的影响
-
扩展性
框架使用者是否可以在不修改框架源码,甚至不拿到框架源码的情况下,为框架扩展新的功能/开发插件。
实践方法
-
TDD(测试驱动开发)
-
Prototype(最小原型):聚焦于一个简单的应用场景,基于此设计实现一个简单的原型,再逐步优化,处理更多细节,如:代码质量、线程安全、性能、扩展性
-
流程图、系统设计图、UML
设计原则
-
依赖注入
-
依赖反转
相关阅读
- 《设计模式:可复用面向对象软件的基础》Design Patterns: Elements of Reusable Object-Oriented Software