xxleyi / learning_list

聚集自己的学习笔记
10 stars 3 forks source link

三种常见编程范式的核心要点 #195

Open xxleyi opened 4 years ago

xxleyi commented 4 years ago

面向过程:状态 函数式:不可变 面向对象:消息传递

以上是我目前认知中,三种编程范式下的三个核心要点。

平时面对问题,应该有的放矢,针对性解决,而不是一味固守某一种编程范式,那样是学究式和极客式风格,不是工业风格。

工业风格讲求的是在现有条件下如何以最低成本最快速的尽量彻底解决问题。

最简单的是函数式的不可变,但不可变本身其实是「分层」思想的体现。计算机就是被用来改变世界状态的,总会有些地方在变化,不可能任何东西都不变,那样根本做不成事。那函数式的「不可变」又是什么呢?这里的「不可变」就是将变化的部分封装到一个便于控制的空间当中。

纯函数式编程语言中,可变部分只有函数的「入参」,靠这部分「可变」,理论上就够了。

但现实条件下,用 Java,用 JS,用 Python 干活赚钱时,不可能做到这样,语言本身就不鼓励,也不支持这样,非要这样做的话,必然要借助外部包,但那样会增加其他维护者的成本,除非团队内部达成一致。但反过来说,任何条件下,函数式编程这种「不可变」思想都是极为有效,且立竿见影的编程策略

比如,我们应该选择写小函数;应该将 IO 部分抽离到单独的某个工具函数里精心维护,以添加多种保护性措施;应该追求每个函数内部只改变自己内部局部变量的状态,尽量避免对入参进行原地修改;修改入参时,优先考虑 copy on write;应该尽量避免把入参直接返回;针对局部变量,应该只有在变量含义不变的情况下才选择重新赋值,典型的例子是循环中的累积类变量。这些可以在任何主流语言中轻松实现,而且也并没有限制任何灵活性手段,看似条条框框很多,其实核心目的只有一个:各种灵活手段停留在函数内部,不污染外部,让可变的东西尽量保持局部性,且具有正向价值。这样的程序绝对更容易维护,也能显著降低 bug 数量。

然后,如果特殊情况下,只有原地修改入参才是合理选择的,应该认真考虑将返回值设为 None,并把此函数视为带副作用的「过程」,目的明确的使用过程的副作用,并严格限制这类情况的个数和范围,绝不滥用。

说了这么说,其实都是在「灵活」贯彻函数式编程的「不可变」思想罢了。

函数式编程触手可及,不要再说函数式编程没有任何实用价值。

回头再谈过程,更很清晰了:过程的核心价值是副作用,某些情况下高效且易用,但比例绝对很低。所以,有意识地区分过程和函数,是一个良好的习惯。

最后再谈面向对象:面向对象的基础是函数闭包、抽象数据类型、高阶函数和可变变量这几个概念性基础,和封装、继承和多态这几个技术性基础。

很明显,面向对象的概念其实是最复杂的,事实上包括了面向过程和函数式。

除去这些,我了解到另一个切入「面向对象」的极佳视角是「消息传递」,就是对象之间依赖消息传递来通讯,从而完成各种计算任务,自身状态发生改变,但「某些全局性质」保持不变。对象之间只能互相传递消息,而不能直接互相修改状态。这才是对象之间恰如其分的界面。但现实是,「方法」替代了「消息传递」,但我们应该知道,「方法」的本质是「消息传递」

因此,即使在使用纯粹面向对象语言,如 Java 时,很多情况下也完全可以使用函数式编程的思想,而且是只使用函数式编程就足够了。此时,仅需发挥 class 组织代码结构的这一最基础功能即可。真的需要进行实打实的面向对象编程时,需要认真把握好抽象、封装和多态的尺度,多用组合,少用继承,并牢记「方法」的本质是「消息传递」

声明性越好的代码,越容易维护。而我们应该追求的所有东西中,易维护应该排在第一位,早晚会排在第一位,而这是践行函数式编程的「核心驱动力」。