今天,我们在软件行业的大部分讨论都是围绕着开发人员的生产力展开的。我们如何让更少的开发者在更短的时间内生产更多的软件?降低前期成本和快速交付基本上是创业公司的口号。快速行动,打破常规。
然而,软件开发的绝大部分时间并不是花在最初的开发阶段。对于任何成功的项目来说,大量的时间都是花在维护该软件上。为了让你的客户满意,继续改进软件、修复错误和提高性能是至关重要的。 如果你的创新能力受到限制,不断地与错误作斗争,提供低劣的用户体验,你的竞争对手就能在市场上胜过你。
功能性编程是过去10年中软件世界的一个重要范式转变。缓慢但肯定的是,它已经从少数不常用的语言的小众功能变成了甚至最成熟的语言的主流。与之前的范式不同,函数式编程的重点不仅是帮助开发人员提高生产力,而且是帮助软件的长期维护。
功能性编程的特点
功能性编程是一个广泛的术语。一些语言将自己描述为函数式,如F#、Haskell和Swift。然而,函数式的特点正在进入其他语言。 Javascript有多个库实现了函数式范式。Rust,一个在系统编程领域相对较新的语言,拥有许多函数式特性。C++和Java多年来一直在增加lambdas和其他函数式特性。无论你的团队使用哪种语言,下面的许多功能都可以实现。
不可变的数据
许多程序,特别是并发程序和网络程序的祸根是数据以意想不到的方式变化。函数式编程主张保持大部分数据的不可变性。一旦创建,数据就不会改变。你可以与程序的其他部分共享这些数据,而不必担心它们被改变或失效。
像Haskell和Rust这样的语言将此作为其实现的基石。C++提供了选择加入不变性的能力。许多Java编码指南建议在可能的情况下默认为不可变的数据。
声明式编程
经典的编程包括指示计算机采取哪些步骤来解决一个问题。例如,要把一个列表中的数字加起来,一个命令式编程方法可能是:
- 创建一个临时变量来保存总和
- 创建一个临时变量来保存当前的索引
- 将索引从0循环到列表的长度
- 将列表中位于索引位置的数值添加到总和中。
这种命令式的方法是有效的,但不能很好地扩展。随着问题变得越来越复杂,命令式方法需要越来越复杂的解决方案。很难在不牺牲性能的情况下将逻辑组件分离成多个独立的循环。而在多核编程时代,创建一个多线程的解决方案需要大量的安全线程处理的专业知识。
在函数式编程中,更倾向于采用声明式的方法。 对一个列表进行求和,通常是这样做的。
- 编写一个函数,将两个值加在一起
- 使用添加函数对列表进行折叠,并将0作为初始值
这种方法自然转化为多核解决方案。库的作者可以写一次并行折叠,而不是每个循环都需要处理复杂的线程管理问题。然后,调用者可以用一个并行的折叠来替换他们的非并行折叠,并立即获得多核的好处。
通过将这种方法与其他声明性编程方法(如映射)相结合,函数式编程可以将复杂的数据管道操作表达为许多单独的、更简单的组件的组成。这就形成了谷歌的MapReduce等知名系统的核心。
强类型化
多年来,业界围绕类型化语言的争论通常是在C++和Java家族与Python和Ruby家族之间展开。前者在编译时引入了一些理智检查,以换取大量明确的类型注释的仪式。相比之下,Python和Ruby则完全跳过了类型注释,将其作为运行时的问题。这提高了生产力,但却牺牲了可维护性。
函数式世界走了一条不同的路:带有类型推理的强大的、表达式的类型系统。类型推理避免了C++风格的类型系统所引入的大量模板,使生产力与Python和Ruby相提并论。函数式语言中的强类型系统允许在类型中表达更多的保证,提高了可维护性,超过了C++和Java的水平。
如今,即使像Python这样的动态类型语言也开始引入类型系统,因为它们展示了巨大的收益。像Rust这样的新语言正在从Haskell和O'Caml这样的函数式语言中借用一些最流行的类型系统特性:和类型、特征等等。
介绍一下函数式编程
值得注意的是,你不需要完全用函数式编程语言重写所有的软件来获得函数式编程的许多好处。你可以从今天开始在你现有的软件中推出功能化的功能,改进你的内部编码准则。 关注上述的一些功能,以及其他许多功能化编程的灵感,是一个很好的开始。
一种选择是通过功能化编程语言的强化培训项目来训练你的团队掌握功能化编程技术。一旦你的团队了解了这些概念,将它们纳入你的Java、Javascript、C#和其他代码库就容易多了。
随着微服务架构的兴起,混合部署模式可能有很大的意义。很多时候,将一个特别关键的业务逻辑卸载到一个单独的、经过良好测试的功能编程代码库中,通过网络API连接,可以减轻团队其他成员的负担,提高软件的稳定性。