[译]每个开发者都需要了解的四种编程范式
FImage by Arek Socha from Pixabay
文中术语
- PP Procedural programming 面向过程编程
- FP Functional Programming 函数式编程
- LP Logical Programming 逻辑编程
- OOP Object-Oriented Programming 面向对象编程
我坚信,作为一个开发者,你需要了解某些事情在你的舒适区之外是如何运作的,即使你从未打算走出舒适区。你知道做一件事情有不同的方法,这一事实本身就为你打开了新方法和技术的视野。
当然,你必须尝试一下,否则,你就不会真正学到东西,然而,从“阅读”到“尝试”的跳跃实际上并不大,所以,对我将在这里提到的4种编程范式中的一种或几种做一下尝试吧。你可能会在做这件事的时候学到一两样东西,如果你碰巧使用的语言与其中一个以上的范式兼容,那么你可以很方便地尝试它们。
开干!
面向过程编程
大多数开发者都是从这个范式开始编程的,即使他们不知道它的名字。然而,一旦他们开始学习范式,他们就会经常把这个范式与函数式编程混淆,毕竟大多数现代编程语言都把 "过程 "这个词改名为 "函数"。这在技术面试中造成了很多尴尬的时刻,当开发人员被问及FP时,他们声称自己已经使用了很多年,但同时却无法回答诸如 "*什么是纯函数?"*或 "什么是HOF?"这样的问题。
PP无非是用程序(又称函数)和常规数据结构工作。我们在这里讨论的不是 "函数式编程",因为这种范式不涉及一些概念(如高阶函数),这些概念是函数式编程独有的。
为什么PP(面向过程编程)如此特殊?
它很容易学习,这就是为什么大多数开发者(包括自学和来自大学等地方的人)倾向于先学习这种编程范式。你不需要了解很多复杂的概念就能掌握它。一旦你理解了什么是基本类型、控制流和条件表达式,下一个合乎逻辑的步骤就是理解如何将这些代码行组合成可以从不同地方多次调用的东西。换言之:一个程序。
许多语言允许你以这种方式工作,因为它们并不强制你使用特定的范式。例如JavaScript、Python、PHP,等等。
在你的职业生涯开始时,这是一个好的范式吗?是的。它有两个主要特点,使它成为首选:首先它很简单,然后你可以用它来做任何事情。
面向过程编程的缺点
作为从事大型项目开发的大型团队来说,它是一个好的范式吗?也许并不是。虽然也许你正在用它构建伟大的软件,但是你要么使用其他范式的概念来扩展这个范式,要么你没有使用更加合适的范式。
面向过程编程的主要问题在于它过于简单化。
- 除非你有模块(module),否则你无法在逻辑上对状态(state)和行为(behavior)进行分组。如果你用它们来模拟类和对象的话,你就没有完成使用面向过程编程,而是利用了其他范式的概念。
- 由于不能使用FP的一些概念,你不得不写指令性代码,这比声明性代码更难解析和理解。换句话说,你不是告诉
编译器/解析器你想在代码执行后干什么,而是要告诉它需要如何发生。这就是"对于这个元素列表中的每个元素,请一个一个地乘以2,并将结果存储在我刚刚创建的这个新列表的末尾 "*(命令式)与"*将这个列表中每个元素的映射之后的结果存储到我刚刚创建的这个新变量中 "*(声明式)之间的区别。 - 由于没有提供优雅的替代方案,就出现了一些典型的不良用法,例如滥用全局变量。当然,你可以在函数调用之间传递属性,但如果你有许多嵌套调用,最终可能会导致你有来自全局上下文的参数(全局变量)被传入函数调用中,仅仅是为了让它们能在3层以下使用。
例子如下:
const myName = "Fernando"
function startProcess(name) {
//...bunch of logic
secondStageOfProcess(name);
}
function secondStateOfProcess(name) {
//...bunch of more logic
startThinkingAboutGreetingUser(name)
}
function startThinkingAboutGreetingUser(name) {
//...even more logic here
sayHi(name)
}
function sayHi(name) {
console.log("Hi there", name, "!")
}
startProcess(myName)
上面的代码是很糟糕的,因为我们必须通过三个不同的函数传递myName常量,然后才能到达真正需要它的逻辑部分。这在代码中产生了很多杂乱无章的东西,使它既难阅读又难维护。
让我们来看看一些替代方案,这些方案扩展了面向过程编程的概念,以创造一个更好的开发体验。
小技巧:通过独立的组件构建一切
告别单体应用,把开发的泪水留在你身后。
未来是组件化和模块化软件的天下,它们速度更快,可扩展性更强,并且可以更简单地构建。像Bit这样的开放源码软件工具为构建独立组件和应用提供了良好的开发体验。许多团队通过Bit来构建他们的设计系统或微前端系统。 尝试一下Bit吧 →
上图是一个独立的源控制和共享的 "Card "组件。右边是=>它的依赖关系图,由Bit自动生成的。
函数式编程
FP是一个非常容易被误解的范式,通常的误解点是它的应用范围,即认为它主要是用在正规教育上。你看,很多正在上大学的开发人员通过Haskell和Clojure等语言学习FP,但他们所涉及的用例通常是面向数学的。
不要误解我的意思,这是一种非常纯粹的学习方式,如果你足够幸运,并且一直在研究这个话题,你会发现FP实际上可以用在许多现实世界的应用中,并且它会使事情变得简单。
然而,事实并非如此,许多开发者并不知道他们有一种基于JVM的语言Scala可以使用,也不知道JavaScript支持FP的许多主要概念,这些都是开箱即用的。
FP有什么优势?
如果你问我为什么喜欢FP,它的首要的优点之一是你可以很容易地写出声明性的代码。
你可以只是描述你的业务逻辑要做什么,而不需要进入实现细节中。
//获得本月要支付的发票清单
let myInvoices = filterOutPaidOnes(
filterByThisMonth(
getInvoices()
)
)
//..or perhaps something like this
let myInvoices = getInvoices(
filterByCurrentMonth(
filterOutPaidOnes
)
)
上面的例子都没有注释,但是你不需要理解语言就能理解逻辑。第一个例子类似于数学上的概念,它与f(g(x))非常相似,你首先需要g(x),然后调用函数f。然后,函数要有名字,把对应的名字给对应的函数你就可以弄懂这块的逻辑。
还有一个优势是,在FP中我们处理的是 "纯函数 "的概念(一个没有副作用的函数,而且每次执行相同的输入都会返回相同的输出),这简化了一些任务,例如:
- 单元测试。测试一些你知道不可能影响其作用域之外的东西要容易得多。
- 更加安全,因为没有不可预见的副作用(即通过调用一个函数,你不会意外地改变一个全局变量)
- 纯函数的签名比普通函数的签名要更有意义。这主要是因为普通函数不必遵循严格的规范,它们可以从外部(甚至是从全局范围)读取数据,其输出也同样不受限制。考虑到纯函数的工作方式,它们没有这种权限,所以函数签名是确定它们可以访问哪些数据以及它们要返回什么(如果有的话)的关键。
FP有哪些缺点?
我知道的最主要缺点是函数式编程通常的教学方式。因为被教导用FP来解决的问题类型所限制(数学问题),学生们通常会忽略这样一个事实:这是一种非常有用的方法。
事实上,FP所使用的语言中很少有在教育背景之外出现的(你最后一次看到Haskell程序在非学术界运行是什么时候?)这意味着,即使他们想使用FP,也很难自己推断出相应的知识,因为他们没有正确的工具来开始。
相反,如果他们也能看到更多的实际使用案例,比如像Python和JavaScript那样将函数式编程与其他范式结合起来使用的方案,那么他们会很快发现其中的好处,并飞快地爱上它。
注意,我不认为Haskel、Clojure或任何其他用于正式教授FP的语言应该被禁止使用。然而,通常采取的 "纯粹主义 "的方法是可能从采取更实用的路线中受益。
逻辑编程
逻辑编程和其他模式十分不同,当涉及到处理问题的解决时,逻辑编程在各个方面都打破了模式。
如果你错过了FP,希望你不要错过这一个。
逻辑编程是另一种非常扎根于数学和逻辑的范式。它不处理代码、循环和条件,而是处理事实和规则。让我解释一下。
在LP的世界里,当你想解决一个问题时,这个解决方案是以一个问题的答案为形式的。为了让你能够提出这个问题,你首先需要一个背景。
因此,你开始陈述一些事实,这些事实基本上是关于实体的真实的东西。比如说。
Einstein is a scientist
太棒了,这就是我们的事实。没有人会质疑这个事实。
其次,我们开始写规则,这是关于我们领域(在这种情况下,关于科学家)的推论。
All scientists are smart
最后是我们的问题,也就是我们一开始就想知道的。
Is Einstein smart?
根据我们的事实和规则,我们问题的答案当然是`是'。虽然这个例子很傻,而且过于简单,但它也显示了逻辑编程所具有的惊人力量。因为我没有写任何一行代码,反正不是我们习惯写的代码。
我没有设置一个聪明人的特征列表,也没有在任何地方写一个IF语句。说实话,我只是直接关注手头的问题,低层次的东西是由解释器推断出来的,很酷吧?
目前主要的LP语言之一是Prolog,虽然你可能从来没有听说过它,但它被用于一些行业内非常小众的领域。如果你想了解更多,并想看到更多有趣的例子,了解如何将LP用于回答一个非常简单的问题之外的事情,请查看。
你是否听说过Prolog?
快速回顾这种鲜为人知的编程语言,以及为什么它有可能塑造你的职业生涯
Prolog有什么缺点?
Yes, there is, but it doesn’t make it useless, it just makes it unknown to most of our community of developers. The main downside is that it works so well at solving very few and very limited set of problems, that most developers (who have never had to face such problems) don’t know about it.
In fact, many developers delving in AI for example, which is one of the main industries where it’s being used right now, use other classical approaches because they’ve never heard of it. That’s how niche LP is!
The other problem with LP is that it’s so different from the way we’re used to code, that honestly considering it as an actual solution to one of our problems becomes a leap of faith. Until you’ve tried it and played around with it long enough, you won’t really be able to understand if this is the right tool for the job. And even if you think you see the potential of LP, making the jump and using it will require a lot of practice and headaches.
Don’t get me wrong, if you’re considering LP, go for it, give it an honest test drive. But make sure you understand that picking up Prolog or any other LP alternative is not like going from PP to FP, the distances are longer and the mental model switch will be bigger.
是的,有,但这并不意味着它是没用的,只是大多数的开发者不知道它。Prolog主要的缺点是,它在解决极少数和极有限的问题上效果很好,以至于大多数开发者(他们从未遇到过这样的问题)不知道它。
事实上,许多深入研究人工智能的开发者(这是目前使用人工智能的主要行业之一)都在使用其他经典方法,因为他们从未听说过它。这就是LP的小众性!
LP的另一个问题是,它与我们习惯的编码方式不同,以至于,把它作为我们某个问题的实际解决方案来考虑,成为一种信仰的飞跃。除非你已经尝试过,并且使用了足够长的时间,否则你将无法真正理解这是否是适合工作的工具。而且,即使你认为你看到了LP的潜力,熟练使用它将需要大量的练习。
不要误会我的意思,如果你正在考虑使用LP,那就去做吧,给它一次诚实的试驾。但是,请确保你明白,拿起Prolog或任何其他的LP替代品并不像从PP到FP那样简单,距离更长,心理模型的转换也会更大。
面向对象编程
最后,OOP是我想介绍的最后一个范式。你们中的大多数人可能都知道并且已经使用了很多年,但我确信其他人不知道,这就是为什么我认为我也要在这里介绍它。
也许你刚刚开始,你使用的语言并不强制执行OOP,但允许你使用面向对象的概念。这是一个很好的入门方式,因为你可以慢慢地开始将越来越多的东西融入到程序化环境中。
那么,OOP是怎么一回事?好吧,在OOP的世界里,你采取一种非常物质的方法,通过类和对象等概念来表示你的问题。换句话说,你试图给你想要处理的实体赋予特性,就好像它们是坐在你身边的物理对象一样。例如,如果你正在考虑代表视频游戏中的门的概念,你可能会创建一个代表对象类型的类,如`游戏对象',它有一些特征,如该对象所在的坐标,它应该出现的游戏场景,甚至可能有一个代表门的命中率的数字值。重点是,你已经把它归类为 "游戏对象",并且通过这种分类,你已经给了它一些属性,这个游戏世界中的其他对象可能会分享这些属性(如果你愿意的话,其他 "游戏对象")。
这种允许你将实体组合在一起的通用分类被称为 "类",每一个符合该类的实体都被称为 "对象"。
请注意,虽然这些概念在我们的代码中用来表示现实世界的对象非常好,但它们可以用来表示任何类型的实体。例如,一个数值的链接列表可以用几个类来表示,这没有什么实际意义。但是,你在头脑中把这些抽象的概念变成物理对象的事实,是使OOP如此直观的原因,至少就该范式的基本概念而言。
OOP有哪些优势?
OOP比FP好在哪里?不应该这样问,它们没有谁好谁坏!它们只是各有优势。
因为要解决问题的不同,有些人可能更认同OOP而不是FP,有些人则喜欢其他的范式。
话虽如此,OOP也有一些优势:
- 根据OOP是如何将抽象概念转换为物理对象的,你可以使用图形(例如UML)来表示这些对象以及它们之间的关系。这使你能在图形中表示复杂业务逻辑的高层次概述。你可以在写下一行代码之前,向他人展示你的想法和概念。
- 这种思考方式很直接。虽然不是每个问题都可以使用OOP来解决,但通过这种思考问题的模式我们可以更容易找到答案。我们的思维方式是与它们打交道并操作它们,所以用它们来思考是我们的第二天性。
- 逻辑就是逻辑应该存在的地方。这样想吧除了门的属性,甚至是
GameObject的属性外,你还想与其他对象进行交互。哪种方式更有意义?是把这些代码放在一个单独的文件中,充满了不相关的函数,还是把所有的东西都放在一个文件中,都放在一个叫做 "类 "的实体的范围内,并确保它的每一个实例(而不是其他)都能访问这些代码? - 有大量的预先建立的模式来帮助你解决你的问题。一部分是因为范式的存在,另一部分是因为OOP的应用非常广泛,以至于许多开发者都想出了设计模式来模板化一些典型的解决方案。例如,如果你需要访问一个只能被实例化一次的实体,你可能会使用单例模式,如果你的逻辑需要观察一些对象并作出反应,你可能想看看Observer模式,等等。它们不是预先建立的解决方案,而是你可以将其纳入你的代码中的模板,以帮助你找到解决问题的最佳方法。
这个列表可以延续下去,它是目前主要的编程范式之一,它已经被分析、研究和拓展到了极限。
OOP是否有弊端?
当然有!当谈到编程范式时,没有银弹,OOP也不例外。
有些问题不适合用物理对象来推理。这意味着,如果采用OOP的方法,你会试图将一个正方形强制塞进一个圆里。如果圆足够大或者足够小,就会很合适,它就会工作,但它永远不会是一个完美的匹配。
OOP的另一个缺点是它太复杂了。我在这里的解释只涉及了OOP的一些表面,我还没有讨论继承、封装、多态以及围绕这一范式而产生的各种花哨词汇。
完全理解和利用OOP多年的阅读和经验的结合。提醒你一下,它的功能并没有因此而减少,但它绝不容易学习。
最后,由于它是一个强大的范式,一些语言把它发挥到了极致,造成了大量的开销,只是为了遵守范式的每一点。让我解释一下:有些语言是100%的OOP,这意味着它们不接受任何其他的选择,如果你用它们来解决简单的问题,你将不得不写更多的模板代码来解决手头的问题。当然,如果你正在开发一个需要大量内部结构的大型应用程序,这是有道理的。因此,这是一个使用它们来解决正确类型问题的问题。
我总是说,编程语言就像工具,你需要为每项工作使用正确的工具,编程范式也是如此。它们并不是为了解决每一个问题,其中一些对于更广泛的范围来说效果更好,而另一些则灵活性较差。在这两种情况下,如果你了解它们帮助解决的问题类型,以及哪些语言可以帮助解决其中的一个问题,你就可以最大限度地利用它们。
作为一个开发者,了解不同的范式将有助于你思考解决每个问题的方式。如果你幸运地使用了一种允许不止一种的语言,你可以在不改变语言的情况下调整你的编码风格。这是练习新范式的好方法,影响很小。
我还漏掉了什么范式,你认为对其他人来说很重要?请在评论中留下你的想法!