架构设计之路 -1
编程范式
结构化编程(Structured programming)
1968年最新提出
它采用子程序、程式码区块(英语:block structures)、for循环以及while循环等结构,来取代传统的 goto。希望借此来改善计算机程序的明晰性、品质以及开发时间,并且避免写出面条式代码。
上图可以发现,虽然没有了goto,但是优秀的我们还是以写出完美的面条式代码。
面向对象式编程 (Object Oriented Programming)
1966 年被提出
面向对象程序设计方法是尽可能模拟人类的思维方式,使得软件的开发方法与过程尽可能接近人类认识世界、解决现实问题的方法和过程,也即使得描述问题的问题空间与问题的解决方案空间在结构上尽可能一致,把客观世界中的实体抽象为问题域中的对象。
一个很好的例子:编年体史书、纪传体史书
函数式编程 (Functional Programming)
函数式编程对于我们是一个比较神秘的编程范式,很少接触到,或者没有发现我们自己接触到。
1958年LISP 基于函数式编程思想被设计出来
函数式编程最大的特点就是没有赋值语句。不允许修改变量的值。
go语言中 函数式编程例子
我们自己代码里的函数式编程
我这里介绍了三种编程范式,它们都从某一方面限制和规范了程序员的能力。
没有一个范式是增加能力的。也就是说每个方式的目的都是设置了限制。
三个范式没有好坏之分,在开发中也没有强制的规定。一切都是以整洁开发为目的。
这三个范式都是在1958年到1968年被提出来的,后续再也没有出现过新的范式。
从编程范式的角度,在这里想起了一段话
二十多岁的程序员特别喜欢并发、框架、协议、内核、中间件之类的名词,还有这些年的大数据、机器学习、VR、区块链之类的新技术。在我不算长的十多年的编程经历里也学过很多很多新的技术,但是不管什么技术都只能维持一段时间的快感:大部分是浮于表面的技术本质没有变化,少数几个本质技术跟几十年前没有什么区别……而对于“优雅的设计”的追求却一直能够带给我精神上的满足感:我一直在追求设计出高内聚、低耦合、易于扩展和维护的系统;代码简洁而高效,考虑周全又没有一丝多余,一切都是恰到好处的感觉:不仅能够应对当前的需求,还能够顺应这段代码、这个系统、这个组织,甚至于自己和这个行业的未来。
在形成了“追求优雅设计”的世界观之后,一切就都不一样了:产品经理的每一个“膈应”的需求都是对自己优雅设计的挑战。程序变得越来越灵活和抽象,细碎的问题渐渐变得不再需要改代码了。未来每一次的修改都在自己的掌控中,三四年前的某个设计可能因为今天的需求而继续产生价值。这是一种开了上帝视角的快感。
如何设计一个好的类
1. 创建一个类的理由
为现实世界建模
为抽象对象建模
降低复杂度,隐藏实现细节,隐藏复杂度
隔离复杂度,限制变动的影响范围
让参数传递更加顺畅
让代码更易于重用
2. 应该避免的类
避免创建万能类
消除无关紧要的类
避免用动词命名的类
3. 坏味道
3.1 抽象
类是否有一个中心目的?
类的命名是否恰当?其名字是否表达了其中心目的?
类的提供的服务是否足够完整,能让其他类无须动用其内部数据?
是否从类中出去无关信息?
是否考虑过把类进一步分解为组件?是否以尽可能将其分解?
3.2 封装
是否把类的成员的可访问性降到最小?
是否避免暴露类的中的数据成员?
在编程语言所许可的范围内,类是否以尽可能地对其他类隐藏自己的实现细节。
类是否避免对其使用者,包括派生类如何使用它做了假设。
类是否不依赖于其他类?他是松耦合的吗?
3.3 继承
继承是否建立一个"IS A 的关系"?也就是说派生类是否遵循了LSP(里氏替换原则)?
派生类是否避免了“覆盖”不可覆盖的方法?
基类中所有的数据成员是否被定义为private的了?
3.4 跟实现相关的其他问题
类中是否有只有大约7个或者更少的数据成员?
是否把类直接或间接调用其他类的子程序数量减少到最少?
类是否只在绝对必要时才与其他类互相协作?
是否在构造函数中初始化了所有数据成员?
3.5 语言相关的问题
你是否研究过所用编程语言里和类相关的各种特有问题?
4. GSX order类演化
原有设计
新的设计
具有了很好的抽象力度。
封装性明显提升。
代码清晰度明显提升。
一点建议
类中所有字段全是可导出的,破坏了封装性
TradeAggregate 主类中的字段过多可以继续拆分。
高质量子程序(函数、方法)
我们为什么要创建子程序呢?
1. 创建一个子程序的理由
降低复杂度
优化一下
引入中间、易懂的抽象(把需要写注释的地方写成子程序)
把一段代码放入一个命名恰当的子程序中,是说明这段代码用意最好的方法
优化一下
避免代码重复
母庸质疑,这是创建子程序最好的理由,我们kit 库就是这个目的。
隐藏顺序
把数据处理的顺序隐藏起来是一个好主意,否则散落在代码里的顺序会带来后续调整的负担。
提高可移植性
可以用子程序来隔离程序中不可移植的部分,从而明确识别未来的移植工作。不可移植的部分包括语言提供的非标准功能,对硬件的依赖,以及对操作系统的依赖等。
此处APP和QT会比较多
简化复杂的布尔判断
为了理解流程,通常没有必要去研究那些复杂的布尔判断的细节。把这些复杂的判断放入函数中,提高代码的可读性。
1.这样就可以把细节放到一边。
2.一个具有描述性的函数名字可以概括出判断的目的。
优化一下
2. 好的名字
避免使用无意义的,模糊或表述不清楚的动词
有时一个子程序仅有的问题就是名字表达不清晰,而程序本身没有问题
准确使用对仗词
add / romove
begin/end
create / destory
fisrt / last
get / put
increment / decrement
insert / delete
lock / unlock
min / max
next / previous
old / new
open / close
show /hide
start /stop
up /down
3. 坏味道
3.1 大局事项
创建子程序的理由充分吗?
一个程序中所有适于单独提出的部分是不是已经被提出到单独的子程序中了?
子程序的名字是否描述了他所有做的事情?
子程序是否只做一件事情,并且做好?(原子性)
3.2 参数传递事宜
子程序中的参数是否超过7个?
是否用到了每一个参数?(不要预留没有用的参数)
子程序是否避免把输入参数当做工作变量?
那么它是否在所有可能的情况下都能返回一个合法值?
防御式编程
1. 关于输入
检查所有来源与外部数据的值
检查子程序所有输入的值
决定如何处理错误的输入值
2. 断言
大声的喊出系统存在一个bug。(响亮的报错!)
断言来处理绝对不应该发生的状况。
喊出来的信息尽量的详细一些,能明确bug类型。
3. 错误处理
健壮性
意味着要不断尝试采取某些措施,以保证程序可以持续的运行下去。哪怕有事做出一些不够准确的结果。
正确性
意味着要不断尝试采取某些措施,以保证程序可以持续的运行下去。哪怕有事做出一些不够准确的结果。
4. 错误处理技术
返回中立值
有时处理错误的最佳做法就是继续执行操作,并简单返回一个没有错误的数值。比如说数值计算可以返回0.字符串可以返回空。
换用下一个正确的数据,或者前次相同的数据
这种处理手法多用于流式数据中,比如天气预报。
换用接近的合法值
有些情况下,可以选择返回最接近的合法值。比如行课的数据。
把警告信息记录到日志文件里
使用这个可以结合其他方法,把错误记录在日志中,然后让程序继续运行,但是使用日志要小心敏感信息是否被记录
当错误发送生时显示出错误信息
要注意不要告诉系统潜在的攻击者太多的东西。攻击者可能利用错误信息来发现如何攻击这个系统。
关闭程序
有些程序一旦出错就要关闭程序,比如在数据迁移时,出现问题就要及时停止脚本。
5. 错误处理的统一
确定一种通用的处理错误的方法,是架构层次的设计决策。
一旦确定了某种错误方法,就要确保始终如一的贯彻这一方法,如果你决定让高层次代码来处理错误,而低层次代码只需要简单的报错,那么就要确保高层次的代码真正的处理了错误。
go 语言是可以忽略函数的返回错误的 --------- 但是千万不要这么做。及时你认为这个函数无论如何不能犯错,也要去检查一下
防御式编程的全部重点就在于【防御那些你未曾预料到的错误】。
BAD CASE
6. 异常
如果一个子程序中遇到了预料之外的情况,但是不知道该如何处理的话,他就可以抛出一个异常,就好比举起双手说“我不知道该怎么处理它-----我真希望有谁知道怎么办!”一样。
对于不知道前因后果的错误,可以把控制权交给系统中其他能更好解释错误的并采取措施的部分。
异常和继承有一点事相同的,审慎明智的使用可以降低复杂度,而草率的使用,只会然代码变得几乎无法理解。
用异常通知程序的其他部分,发送了不可忽略的错误
只有在真正例外的情况下使用异常
不能用异常来推卸责任
避免使用空 catch语句
7. 坏味道
子程序是否保护自己免遭有害输入数据的破坏?
你是否在高层设计中规定了是让错误处理倾向于健壮性或者稳定性?
是否在开发阶段使错误不可以被忽略?
是否考虑到SQL注入和XSS攻击?
是否检查了所有错误?
是否捕获了所有异常
错误信息中是否泄漏了机密数据?
错误信息是否暴露太多系统细节,会被攻击者攻击?
版权声明: 本文为 InfoQ 作者【Dnnn】的原创文章。
原文链接:【http://xie.infoq.cn/article/ec1d106bfa0857d5c86b88dd6】。未经作者许可,禁止转载。
评论