商业竞争的本质在于成本和效率,软件架构架构也是如此, 终极目标是用最小的人力成本满足架构和维护该系统。因此,如果一个产品的版本迭代成本巨大,修改的代码比新增的代码还多,那么这个产品的架构一定是失败的。
衡量一个软件架构的优劣可以用一个简单且有效的模式,就是看随着团队和软件规模的扩大,工程师的个人产出有没有下降。也就是1+1是否等于2,还是远远小于2.
工程师常常说,我们需要先保证交付需求,至于设计和代码质量我们可以未来在重构,工程师在说这样的话时可能真的这么想的,但很快就会忘记了。所以,作为工程师,永远不要给自己代码重构的机会,工程是需要为代码重构行为付出代价。
在实践中应该怎么做呢?
1,这开始编码之前,是否经过精心的设计?
2,实际的编码和自己的设计是否差距甚远?
3,别的工程师是否可以根据这个设计进行开发,而在集成过程中无需修改?
4,工程师的主要日常工作是不是仅仅按照需求文档编写代码并修复bug?
行为价值 VS 架构价值
行为价值是为了完成产品目标,但架构价值是除了完成目标外,还以更小的成本达到可维护性,这样做可能会花费一些时间,但都是值得的。简单来说,就是“紧急”和“重要”哪个更有优先。业务部门有时候没有能力评估架构的重要性,因此工程师需要坚守底线,“重要”比“紧急”更优先。不能为了快牺牲了质量或者陷入扩展、维护的泥潭。这需要工程师和业务部门“坚持斗争”。
编程范式
1,结构化编程
它采用子程序、块结构、for循环以及while循环等结构,来取代传统的goto,是对代码无限跳转的一种限制。很明显,不加限制的goto语句让代码难以控制和维护,几乎所有的语言都支持结构化编程范式,这是现代编程语言的基础。
2,面向对象
面向对象的本质是什么?封装、继承、多态?封装的目的有两个,1:让模块使用者使用更简单,暴露的信息越少使用起来越方便;2:非开源代码隐藏代码实现。在这方面,C语言更胜一筹,它可以通过头文件和指针来隐藏源码的任何实现细节,因此封装性不是面向对象的本质。那么集成呢?如果大家了解brew的话会非常清楚这一天,brew系统是使用C语言实现的,完全模拟了继承,因此继承也不是面向对象的本质,那么是多态吗?如何理解多态?
多态首先是一种面向接口的编程思想,这种思想在累的实现上表现为多态,而在模块的集成上一样可以有多态。例如书中举例在unix上IO操作,输入来源STDIN,我们只需要对它定义一种接口,也是一种约定。这个接口的使用者只需要简单地调用这些接口而无需关心它的具体实现。
多态(或称面向接口)的应用举例:
例1:STDIN,在unix中,一切的数据交换都成为流(stream),所有的流都遵守了一个约定,也就是实现了一套接口,open、close、read、write、seek,因此对流的操作可以任何切换和移植
例2:我在去年开发了一套Flutter异常采集和上报的组件,这个组件首先分成成两部分,采集和上报,两者相互独立。其次,对上报而言采用的就是面向接口的设计,可以上报的控制台、弹窗、文件、百度性能平台等,只要你愿意,甚至可以多种途径的组合。
/// 上报异常的接口
abstract class FXCErrorReporter {
Future<bool?> reportException(
FXCExceptionData data, Map<String, dynamic> extra);
}
/// 控制台输出
class FXCReporterConsole implements FXCErrorReporter
/// 弹窗
class FXCReporterDialog implements FXCErrorReporter
/// 文件
class ECReporterFile implements FXCErrorReporter
例3:前段时间我们在开发群聊功能时候有过一个设计方面的讨论,就是如何把一个较为复杂的功能进行分层,对于一个IM模块而言至少有三个部分:UI、IM消息的接收和发送、数据存储。但无论如何,对于负责UI开发的同学应该只关注数据的拉去、发送和展示,至于这些数据是从IM来的还是哪里来的无需关心,数据的存储更不需要关系。从这个角度看,这也是一种多态,UI层和下层之间应该有一套接口,它提供拉消息、发消息的功能,仅此而已。具体这些消息哪来的,是从缓存读取的还是IM接收的,又是如何发出去的,上层为什么要关心呢。
因此,多态是只是一种手段,每一种语言有自己的实现,C语言可以使用函数指针,C++可以使用虚函数表,Objc用runtime,但不管如何,多态不是面向对象的首创,只是把它发扬光大而且借助于编译器让它更加好用。而面向对象的本质就是通过多态这种手段对代码中的依赖关系进行控制。让编程变得插件化、可插拔。
3,函数式编程
软件的bug产生的原因通常都是变量的值变化导致的,因此函数式编程主张不存储变量,通过一层又一层的函数来完成计算,一个函数的输出作为另一个函数的输入。在架构实践中可以应用的是可变性隔离,也就是把可变的和不可变的隔开,这是一个重要思想。同样上面的群聊模块也可以从这个角度出发,不可变的部分是UI、IM模块、可变的部分很少就是用户的操作记录和消息缓存。
衡量一个软件架构的优劣可以用一个简单且有效的模式,就是看随着团队和软件规模的扩大,工程师的个人产出有没有下降。也就是1+1是否等于2,还是远远小于2.
工程师常常说,我们需要先保证交付需求,至于设计和代码质量我们可以未来在重构,工程师在说这样的话时可能真的这么想的,但很快就会忘记了。所以,作为工程师,永远不要给自己代码重构的机会,工程是需要为代码重构行为付出代价。
在实践中应该怎么做呢?
1,这开始编码之前,是否经过精心的设计?
2,实际的编码和自己的设计是否差距甚远?
3,别的工程师是否可以根据这个设计进行开发,而在集成过程中无需修改?
4,工程师的主要日常工作是不是仅仅按照需求文档编写代码并修复bug?
行为价值 VS 架构价值
行为价值是为了完成产品目标,但架构价值是除了完成目标外,还以更小的成本达到可维护性,这样做可能会花费一些时间,但都是值得的。简单来说,就是“紧急”和“重要”哪个更有优先。业务部门有时候没有能力评估架构的重要性,因此工程师需要坚守底线,“重要”比“紧急”更优先。不能为了快牺牲了质量或者陷入扩展、维护的泥潭。这需要工程师和业务部门“坚持斗争”。
编程范式
1,结构化编程
它采用子程序、块结构、for循环以及while循环等结构,来取代传统的goto,是对代码无限跳转的一种限制。很明显,不加限制的goto语句让代码难以控制和维护,几乎所有的语言都支持结构化编程范式,这是现代编程语言的基础。
2,面向对象
面向对象的本质是什么?封装、继承、多态?封装的目的有两个,1:让模块使用者使用更简单,暴露的信息越少使用起来越方便;2:非开源代码隐藏代码实现。在这方面,C语言更胜一筹,它可以通过头文件和指针来隐藏源码的任何实现细节,因此封装性不是面向对象的本质。那么集成呢?如果大家了解brew的话会非常清楚这一天,brew系统是使用C语言实现的,完全模拟了继承,因此继承也不是面向对象的本质,那么是多态吗?如何理解多态?
多态首先是一种面向接口的编程思想,这种思想在累的实现上表现为多态,而在模块的集成上一样可以有多态。例如书中举例在unix上IO操作,输入来源STDIN,我们只需要对它定义一种接口,也是一种约定。这个接口的使用者只需要简单地调用这些接口而无需关心它的具体实现。
多态(或称面向接口)的应用举例:
例1:STDIN,在unix中,一切的数据交换都成为流(stream),所有的流都遵守了一个约定,也就是实现了一套接口,open、close、read、write、seek,因此对流的操作可以任何切换和移植
例2:我在去年开发了一套Flutter异常采集和上报的组件,这个组件首先分成成两部分,采集和上报,两者相互独立。其次,对上报而言采用的就是面向接口的设计,可以上报的控制台、弹窗、文件、百度性能平台等,只要你愿意,甚至可以多种途径的组合。
/// 上报异常的接口
abstract class FXCErrorReporter {
Future<bool?> reportException(
FXCExceptionData data, Map<String, dynamic> extra);
}
/// 控制台输出
class FXCReporterConsole implements FXCErrorReporter
/// 弹窗
class FXCReporterDialog implements FXCErrorReporter
/// 文件
class ECReporterFile implements FXCErrorReporter
例3:前段时间我们在开发群聊功能时候有过一个设计方面的讨论,就是如何把一个较为复杂的功能进行分层,对于一个IM模块而言至少有三个部分:UI、IM消息的接收和发送、数据存储。但无论如何,对于负责UI开发的同学应该只关注数据的拉去、发送和展示,至于这些数据是从IM来的还是哪里来的无需关心,数据的存储更不需要关系。从这个角度看,这也是一种多态,UI层和下层之间应该有一套接口,它提供拉消息、发消息的功能,仅此而已。具体这些消息哪来的,是从缓存读取的还是IM接收的,又是如何发出去的,上层为什么要关心呢。
因此,多态是只是一种手段,每一种语言有自己的实现,C语言可以使用函数指针,C++可以使用虚函数表,Objc用runtime,但不管如何,多态不是面向对象的首创,只是把它发扬光大而且借助于编译器让它更加好用。而面向对象的本质就是通过多态这种手段对代码中的依赖关系进行控制。让编程变得插件化、可插拔。
3,函数式编程
软件的bug产生的原因通常都是变量的值变化导致的,因此函数式编程主张不存储变量,通过一层又一层的函数来完成计算,一个函数的输出作为另一个函数的输入。在架构实践中可以应用的是可变性隔离,也就是把可变的和不可变的隔开,这是一个重要思想。同样上面的群聊模块也可以从这个角度出发,不可变的部分是UI、IM模块、可变的部分很少就是用户的操作记录和消息缓存。