少于 1 分钟阅读

简介

串一串编程范式

架构整洁之道, 看这一篇就够了!

架构是软件系统的一部分,所以要明白架构的价值,首先要明确软件系统的价值。软件系统的价值有两方面,行为价值和架构价值。

  1. 行为价值是软件的核心价值,包括需求的实现,以及可用性保障(功能性 bug 、性能、稳定性)。这几乎占据了我们90%的工作内容,支撑业务先赢是我们工程师的首要责任。如果业务是明确的、稳定的,架构的价值就可以忽略不计,但业务通常是不明确的、飞速发展的,这时架构就无比重要,因为架构的价值就是让我们的软件(Software)更软(Soft)
  2. 架构价值

    1. 当需求变更时,所需的软件变更必须简单方便。
    2. 变更实施的难度应该和变更的范畴(scope)成等比,而与变更的具体形状(shape)无关。

实现行为价值的需求通常是 PD 提出的,都比较紧急,但并不总是特别重要;架构价值的工作内容,通常是开发同学提出的,都很重要但基本不是很紧急,短期内不做也死不了。我们开发同学,在低头敲代码之前,一定要把杂糅在一起的“重要且紧急”和“不重要但紧急”分开,把我们架构工作(“重要但不紧急”)插进去。

其实所谓架构就是限制,限制源码放在哪里、限制依赖、限制通信的方式,但这些限制比较上层。编程范式是最基础的限制,它限制我们的控制流和数据流:结构化编程限制了控制权的直接转移(限制了goto语句),面向对象编程限制了控制权的间接转移(限制了函数指针的使用),函数式编程限制了赋值

编程范式   描述  
非结构化编程 其实就是汇编随便写 用到哪块逻辑,就 goto 到哪里执行一段代码 很难预测代码的执行结果,代码规模稍微一大,就几乎难以维护了
结构化编程 限制控制权的直接转移 就是函数调用或者 goto 语句,代码在原来的流程里不继续执行了,转而去执行别的代码,并且你指明了执行什么代码。 限制goto语句
面向对象编程 限制控制权的间接转移 就是代码在原来的流程里不继续执行了,转而去执行别的代码,但具体执行了啥代码你也不知道,你只调了个函数指针或者接口。 限制函数指针
函数式编程 限制赋值 函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值  

没有结构化编程,程序就无法从一块块可证伪的逻辑搭建,没有面向对象编程,跨越组件边界会是一个非常麻烦而危险的过程,而函数式编程,让组件更加高效而稳定。没有编程范式,架构设计将无从谈起

架构工作的基本方针

  1. 尽可能长时间地保留尽可能多的可选项。
  2. 低层次解耦方式能解决的,不要用高层次解耦方式。

组件拆分需要在两个维度进行:按层次拆分、按变更原因拆分。

许式伟:编程范式的进化

  1. 过程式编程中最核心的两个概念是结构体(自定义的类型)和过程(也叫函数)。通过结构体对数据进行组合,可以构建出任意复杂的自定义数据结构。通过过程可以抽象出任意复杂的自定义指令,复用以前的成果,简化意图的表达。但数据结构和过程的关系是非常松散的。
  2. 函数式本质上是过程式编程的一种约束,它最核心的主张就是变量不可变,函数尽可能没有副作用(对于通用语言来说,所有函数都没副作用是不可能的,内部有 IO 行为的函数就有副作用)。
  3. 面向对象在过程式的基础上,引入了对象(类)和对象方法(类成员函数),它主张尽可能把方法(其实就是过程)归纳到合适的对象(类)上,不主张全局函数(过程)。
    1. 封装。面向对象不主张绕过对象的使用接口侵入到对象的内部实现细节。因为这样做破坏了信息的封装,降低了类的可复用性,有一天对象的内部实现方式改变了,依赖该对象的相关代码也需要跟着调整。PS:多想想一个对象的输入输出是什么
    2. 多态。由于对象和对象方法的强关联,我们可以引入接口来抽象不同对象相同的行为(比如鸟和猪是不同的对象,但是它们有相同的方法,比如移动和吃东西)。这样(对调用方来说)不同对象就可以用相同的代码来实现类似的复杂行为,这就是多态了。PS:是不是可以认为封装和多态都是为了对调用方屏蔽变化
    3. 大家对继承褒贬不一。这件事情最完美的答案是 Go 语言给出来的:放弃继承,全面强化组合能力

《C++ Primer Plus》计算机语言要处理两个概念——数据和算法。c最初是过程性语言,意味着它强调的是编程的算法方面。OOP强调的是数据,试图让语言来满足问题的要求,理念是设计与问题的本质特性相对应的数据格式。在C++中,类是一种规范,它描述了这种新型数据格式,对象是根据这种规范构造的特定数据结构。它不是将重点放在任务上,而是放在概念上。

多范式编程

多范式编程会越来越多,是因为我们的关注点是做出好的设计,写出更容易维护的代码,所以,我们会尝试着把不同编程风格中优秀的元素放在一起。比如,我们采用面向对象来组织程序,而在每个类具体的接口设计上,采用函数式编程的风格,在具体的实现中使用结构化编程提供的控制结构

面向对象创造性地把契约的重要性提高到了非常重要的高度,但这还远远不够。这是因为,并不是只有对象需要契约,语言设计的方方面面都需要契约。

  1. 比如,代码规范约束了人的行为,是人与人的连接契约。如果面对同一种语言,大家写代码的方式很不一样,语言就可能存在很多种方言,这对达成共识十分不利。所以 Go 语言直接从语言设计上就消灭掉那些最容易发生口水的地方,让大家专注于意图的表达。PS:go 的一个设计理念就是 实现一个目的只能有一种实现方式
  2. 消息传递约束了进程(这里的进程是抽象意义上的,在 Go 语言中叫 goroutine)的行为,是进程与进程的连接契约。消息传递是多核背景下流行起来的一种编程思想,其核心主张是:尽可能用消息传递来取代共享内存,从而尽可能避免显式的锁,降低编程负担。PS:虽然本质都是共享内存,但并发处理的方式统一了。

符号式编程

命令式编程

def add(a,b):
    return a + b
def fancy_func(a,b,c,d):
    e = add(a,b)
    f = add(c,d)
    g = add(e,f)
    return g
print(fancy_func(1,2,3,4))

对应的符号式编程

def add_str():
    return '''
    def add(a,b):
        return a + b
    '''
def fancy_func_str():
    return '''
    def fancy_func(a,b,c,d):
        e = add(a,b)
        f = add(c,d)
        g = add(e,f)
        return g
    '''
def evoke_str():
    return add_str() + fancy_func_str() + '''
    print(fancy_func(1,2,3,4))
    '''
prog = evoke_str()
y = compile(prog,'','exec')
exec(y)

定义的三个函数以字符串形式返回计算流程,最后通过compile 编译完整的计算流程并执行,由于编译器能够完整的获取整个程序,因此有更多空间优化计算。

标签:

分类:

更新时间:

留下评论