过程式、函数式、命令式、声明式编程模式不同点

·  阅读 2485

The Differences Between Procedural, Functional, Imperative, and Declarative Programming Paradigms

共四种编程模式,没有优劣,某一种或几种也不是银弹,也不存在鄙视链,只有适合的场景。但是在某种场景下选用特定的模式对代码会带来更好的可读性、可维护性和减少代码出错的概率,故值得我们去学习他们的不同之处。

过程式编程

过程式编程即『Procedural Paradigm』用一系列流程去完成任务,比如 VBScript 中的 procedure,没有返回值,存在副作用。

举例

VB 中的调用(Procedures)通常是为了组织代码和复用代码,分为函数(Function Procedures)调用和过程(Sub Procedures)调用。

二者的区别是函数有返回值,过程无返回值,有副作用,因为就是通过副作用达到其目的。

💡 VBScript Tips 当一个过程无需返回值,则可以使用 sub procedure

Sub outputMessage()
document.write("Welcome")
End Sub
复制代码

该例子,调用了 document.write("Welcome"),对 document 产生了『副作用』。

函数式编程

『Functional Paradigm』用一系列函数去完成任务,有返回值,函数一般没有副作用,即入参决定出参,且单测友好。

举例

第一个例子还是来自 VBScript

Function findArea(radius)
   const pi=3.14
   area = pi*radius*radius
   findArea = area
End Function
复制代码

计算面积函数返回面积,有返回值,无副作用,输入决定输出,相同的入参无用调用多少次,输出都是一样的。

函数式编程的另一个特点是函数可以当做参数,以 ramda.js#map 为例

const double = x => x * 2;

R.map(double, [1, 2, 3]); //=> [2, 4, 6]
复制代码

PlainJS

const double = x => x * 2;

[1, 2, 3].map(double); //=> [2, 4, 6]
复制代码

命令式编程

"How to do it, not what to do

『Imperative Paradigm』强调实现细节,focus on How not What

举例

比如用 for 循环实现累加,我们会关注诸多实现细节,需要遍历所有项,需要关注遍历起始点,需要小心翼翼不能越界,

index++ 还可以多种写法等诸多细节,最后才是将其加起来。我们的目标『加起来』被淹没在重重细节中。

let sum = 0;
const users = [{ age: 18 }, { age: 28 }, { age: 38 }];

for (let index = 0; index < users.length; index++) {
  const user = users[index];

  sum += user.age;
}
复制代码

声明式编程

"what to do, not how to do it."

『Declarative Paradigm』只声明目标不指定细节,focus on What not How

举例

使用声明式编程,我们更多关注在我们的目标,我们要什么,细节统统托管给库或更底层。

例一:mysql 实现累计

mysql 其实就是声明式编程的最佳范例。

select sum(age) from user where gender = male
复制代码

翻译成中文『求 user 表中所有男性的年龄和』,关注点直驱目标『去和 sum(age)』,没有啰嗦的实现细节。

例二:reduce 实现累计

用 reduce 实现累加,我们关注点在做『累加』即 acc + num,这就是我们的目标,代码通常更简洁,因为无需遍历,也没有中间变量。

const users = [{ age: 18 }, { age: 28 }, { age: 38 }];

users.reduce((acc, user) => acc + user.age, 0);
复制代码

声明式编程因为隐藏了细节,底层的实现会随着架构的优化不断优化,也就是同一份代码随着时间变化,速度将越来越快。以 java 的函数式编程为例。串行写法如下:

List<User> users = Arrays.asList(new User("John", 30), new User("Julie", 35));

int result = users.stream()
  .reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge(), Integer::sum);

assertThat(result).isEqualTo(65);
复制代码

同样只用关注累加 Integer::sum 我们甚至无需实现加法这个函数。

并发只需将 stream 改成 parallelStream

List<User> users = Arrays.asList(new User("John", 30), new User("Julie", 35));

- int result = users.stream()
+ int result = users.parallelStream()
  .reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge(), Integer::sum);

assertThat(result).isEqualTo(65);
复制代码

就可以享受到多核带来的额外红利。

总结

声明式编程比命令式抽象层次更高。没有绝对的谁好谁好,但一定要用对场景。

  • 声明式编程通常有框架或库辅助,解决通用的业务问题够用了,故日常开发中我们应该鼓励使用声明式编程模式

  • 命令式编程通常在我们实现某种算法或实现一个框架或库需要,故日常开发我们应该尽量避免,因为其关注点分散,不容易阅读,而且细节多中间变量多意味着出错概率更大

  • 函数式编程是声明式的子集。

  • 过程式编程是命令式的子集。

The Differences Between Procedural, Functional, Imperative, and Declarative Programming Paradigms

我们初学代码的进化过程一般是

  • 过程式到函数式

  • 命令式到声明式

参考

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改