不要再尝试函数式编程了

更新日期: 2019-08-18阅读: 1.9k标签: 编程

也许你曾听说过所谓的“函数式”编程。也许你甚至在想接下来是否要尝试一下。但是,函数式编程有很多缺陷,并不适用于现实项目的开发,并且会造成工作效率的下降。欲知详情,且听本文娓娓道来 。【译者注:本篇采用了讽刺的写法,若急于知道真相,请拉至文末。】

也许你曾听说过所谓的“函数式”编程。也许你甚至在想接下来是否要尝试一下。

答案是别 !它简直是地狱!

函数式编程有很多缺陷,并不适用于现实项目的开发,并且会造成工作效率的下降。为什么呢?且听本文娓娓道来!


函数式编程无法满足复杂的企业需求

现实世界中的企业级软件需要满足一系列复杂的、严格的、强制性的需求,这些需求与大量内嵌于软件解决方案中的抽象预期相关。换句话说,面向对象编程有助于程序员使用多种抽象机制,这些抽象机制完全能够满足企业的复杂需求。

这读起来有点拗口,但请先忍一下!接下来将做清晰地解释。

所谓的“函数式”编程,由于是基于数学的,所以没有合适的抽象机制(显然,这么做不太好,除了在学术界,数学在现实世界中没有任何应用)。与 OOP 不同,函数式编程并没有试图去满足企业所要求的众多严格而复杂的需求。

这段代码演示了函数式编程中普遍存在的问题:

import{ filter, first,get}from'lodash/fp';

constfilterByType =type=>
filter(x=>x.type ===type);

constfruits = [
{type:'apple', price:1.99},
{type:'orange', price:2.99},
{type:'grape', price:44.95}
];

constgetFruitPrice =type=>fruits =>
fruits
|> filterByType(type)
|> first
|>get('price');

constgetApplePrice = getFruitPrice('apple');

console.log('apple price', getApplePrice(fruits));

看着它生不生气?没事,不光是你!

函数式编程并没有像所有严肃的企业通常所要求的那样,尝试对功能进行适当的抽象和封装。

任何一个有自尊心的软件工程师都不会写这样的代码!如果他们这样做了,那么他们可能会被那些严肃的大型企业立即解雇,以防止进一步的损失。在下一节中,我们将研究一个适当抽象的 OOP 程序。


函数式软件解决方案并不是面向未来的

大家都清楚,一个专业且有自尊心的软件工程师的首要职责是,编写能够满足复杂业务需求且面向未来的代码。

与上面那段灾难性的函数式代码相比,让我们快速地看一个适当抽象的 OOP 程序。它做的事情完全相同,但采取的却是一种抽象且面向未来的方式:

classFruit{
constructor(type,price){
this.type=type;
this.price = price;
}
}

classAbstractFruitFactory{
make(type,price){
returnnewFruit(type,price);
}
}

classAppleFactoryextendsAbstractFruitFactory{
make(price) {
returnsuper.make("apple", price);
}
}

classOrangeFactoryextendsAbstractFruitFactory{
make(price) {
returnsuper.make("orange", price);
}
}

classGrapeFactoryextendsAbstractFruitFactory{
make(price) {
returnsuper.make("grape", price);
}
}

classFruitRepository{
constructor() {
this.fruitList = [];
}

locate(strategy) {
returnstrategy.locate(this.fruitList);
}

put(fruit) {
this.fruitList.push(fruit);
}
}

classFruitLocationStrategy{
constructor(fruitType) {
this.fruitType = fruitType;
}

locate(list) {
returnlist.find(x => x.type===this.fruitType);
}
}

classFruitPriceLocator{
constructor(fruitRepository, locationStrategy) {
this.fruitRepository = fruitRepository;
this.locationStrategy = locationStrategy;
}

locatePrice() {
returnthis.fruitRepository.locate(this.locationStrategy).price;
}
}

const appleFactory =newAppleFactory();
const orangeFactory =newOrangeFactory();
const grapeFactory =newGrapeFactory();

const fruitRepository =newFruitRepository();
fruitRepository.put(appleFactory.make(1.99));
fruitRepository.put(orangeFactory.make(2.99));
fruitRepository.put(grapeFactory.make(44.95));

const appleLocationStrategy =newFruitLocationStrategy("apple");

const applePriceLocator =newFruitPriceLocator(
fruitRepository,
appleLocationStrategy
);

const applePrice = applePriceLocator.locatePrice();

console.log("apple", applePrice);

正如我们所看到的那样,它 SOLID 对所有的核心功能都进行了适当的抽象。这段代码是 SOLID 的。

不要让简单的东西愚弄了你!它完全满足通常任何大型企业所要求的所有的复杂业务需求。

这个健壮的解决方案完全是面向未来的,并且适当地利用了企业级依赖注入。


严肃的管理需要严肃的功能

希望到目前为止,开发团队已经按照企业的规定,完成了与代码抽象相关的复杂业务需求。开发人员现在应该把资源重点投入到实现项目经理定义的功能上。

现实中的任何企业产品经理都知道,只有交付的新功能才是真正具有业务价值的。开发人员不应该将资源浪费在诸如单元测试、重构等耗时的事情上。

很显然,所谓的“函数式”编程是有缺陷的,它没必要使像重构、单元测试等多余的工作变得那么简单。这反过来又会分散开发团队的注意力,开发人员可能会不小心地将时间浪费在那些无用的活动上,而不是提供新的功能。

下面的例子非常清楚地展示了函数式编程的劣势,它使重构变得过于简单了:

// 重构之前:

// calculator.js:
constisValidInput =text=>true;

constbtnAddClick =(aText, bText) =>{
if(!isValidInput(aText) || !isValidInput(bText)) {
return;
}
}

// 重构之后:

// inputValidator.js:
exportconstisValidInput =text=>true;

// calculator.js:
import{ isValidInput }from'./inputValidator';

constbtnAddClick =(aText, bText, _isValidInput = isValidInput) =>{
if(!_isValidInput(aText) || !_isValidInput(bText)) {
return;
}
}

如果这样的重构让你对它的简单性感到不安,你并不是唯一的一个!重构前有六行代码,重构后只有七行代码?你一定是在开玩笑吧!

让我们将其与面向对象代码的适当重构进行对比:

// 重构之前:
publicclassCalculatorForm{
privatestringaText, bText;

privateboolIsValidInput(stringtext)=>true;

privatevoidbtnAddClick(objectsender, EventArgs e){
if( !IsValidInput(bText) || !IsValidInput(aText) ) {
return;
}
}
}

// 重构之后:
publicclassCalculatorForm{
privatestringaText, bText;

privatereadonlyIInputValidator _inputValidator;

publicCalculatorForm(IInputValidator inputValidator){
_inputValidator = inputValidator;
}

privatevoidbtnAddClick(objectsender, EventArgs e){
if( !_inputValidator.IsValidInput(bText)
|| !_inputValidator.IsValidInput(aText) ) {
return;
}
}
}

publicinterfaceIInputValidator{
boolIsValidInput(stringtext);
}

publicclassInputValidator:IInputValidator{
publicboolIsValidInput(stringtext)=>true;
}

publicclassInputValidatorFactory{
publicIInputValidatorCreateInputValidator()=>newInputValidator();
}

这才是正确编程的样子!重构前 9 行代码,重构后 22 行代码。重构需要付出更多的努力,这将促使企业开发人员在进行诸如重构之类的浪费资源的活动之前能三思而后行。


声明式代码的谬论

所谓的“函数式”程序员错误地以编写声明式代码为荣。这没什么值得骄傲的,这种代码只是制造了一种生产力的假象。

任何开发人员的核心职责都应该包括进行适当且严格的面向对象抽象(这也是任何大型企业所要求的)。

让我们来看一段适当抽象的 OOP 代码:

classCountryUserSelectionStrategy{
constructor(country) {
this.country = country;
}

isMatch(user) {
returnuser.country ===this.country;
}
}

classUserSelector{
constructor(repository, userSelectionStrategy) {
this.repository = repository;
this.userSelectionStrategy = userSelectionStrategy;
}

selectUser() {
letuser =null;

for(constuinusers) {
if(this.userSelectionStrategy.isMatch(u) ) {
user = u;
break;
}
}

returnuser;
}
}

constuserRepository =newUserRepository();
constuserInitializer =newUserInitializer();
userInitializer.initialize(userRepository);

constamericanSelectionStrategy =newCountryUserSelectionStrategy('USA');
constamericanUserSelector =newUserSelector(userRepository, americanSelectionStrategy);

constamerican = americanUserSelector.selectUser();

console.log('American', american);

请关注第 20 行的循环命令。忽略次要的样板 OOP 代码,它与当前任务无关。为了使代码示例符合严肃企业提出的严格抽象要求,必须包含它。

另一方面,声明式代码过于简洁,并且错误地引导开发人员将注意力集中在不太重要的事情上,比如业务逻辑。将上述健壮的企业解决方案与下面这段较差的“声明式”代码进行对比:

SELECT *FROMUsersWHERECountry=’USA’;

SQL 每次都让我感到害怕,因为它是声明式的。为什么选择 SQL 呢?为什么它们不能让开发人员使用适当的企业级抽象并编写正常的面向对象的代码呢?特别是当我们已经拥有了这些工具时。这真让人吃惊。


现实世界建模

面向对象编程简直是天才。与“函数式”编程不同,它使用继承、多态和封装等高级技术能完美地为现实世界建模。

任何有自尊心的软件开发人员都应该每天使用继承来实现代码的可重用性。正如我之前所说,继承完美地模拟了现实世界。例如,猫总是从一个抽象的现实世界中的动物身上继承它们的特性和行为。生命起源于几十亿年前的海洋。因此,所有哺乳动物(包括猫)都继承了原始鱼类的特性(如 garfield.fishHead )以及方法(如 garfield.swim 和 garfield.layCaviar )。难怪猫这么喜欢洗澡 和游泳!人类其实是一样的,如果我们愿意,我们也可以很容易地开始产卵!

对于代码组织,我们的程序应该始终遵循类似的分层方法。函数式编程错误地将开发人员从受现实世界启发所得到的如此惊人的代码共享结构中解脱出来。这会产生深远的影响,特别是在非常复杂的企业软件中。


函数应始终与对象绑定

这只是常识,也是对现实世界的完美建模。你在 Chapters 购买的笔记本带有内置的“写方法”。每当你打算写东西的时候,都要调用这个方法。你可能没有意识到这一点,但你还有其他一些方法,比如.eat(veggies))、doHomeWork。这只是常识,不然你妈妈怎么能让你吃蔬菜,让你完成家庭作业呢?当然,她过去常常直接调用这些方法!

在现实世界中,如果没有一个专门负责协调任务的 Manager,工作是不可能完成的。年轻人可能需要一个管理者来满足他们基本需求,比如“netflix-n-chill”。到底由谁来协调整个过程?如果他们聪明的话,就会像 OOP 推荐的那样,雇佣多个管理者。

在现实世界中,创造任何新的、酷的东西也都需要有一个专门的 Factory。Leonardo 拥有一个 MonaLisaFactory,Trump 建造了一个秘密的 WallFactory。俄罗斯过去有一个 CommunismFactory,现在主要维护它隐藏在克里姆林宫地下深处的 CorruptionFactory。

我们可以清楚地看到,这只是“函数式”棺材中的另一颗钉子,因为它没有试图模拟现实世界。允许函数独立于对象存在,这显然是错误的。显然,函数编程不适用于任何现实际的编码。


函数式编程没有提供成长的机会

首先最重要的是,软件工程师应该专注于持续的提升和成长。为了真正掌握面向对象的编程,软件工程师必须掌握大量的知识。

首先,他们必须学习高级 OOP 技术,如继承、抽象、封装和多态。然后,他们应该熟悉各种设计模式(比如单例模式)并在代码中使用。大约有 30 种基本的设计模式需要学习。此外,理想情况下,开发人员也应该在代码中使用各种企业级抽象技术。

其次,是熟悉领域驱动设计之类的技术,并学习如何分解单体应用。还建议他们学习下合适的重构工具,比如 Resharper,因为 OOP 代码重构起来并不容易。

至少需要 20-30 年的时间才能熟练掌握 OOP。即使如此,大多数有 30 年 OOP 经验的人也还没有真正掌握它。学习之路坎坷不平,充满了不确定性。OOP 开发者需要终生学习,这是多么令人兴奋啊!

那么可怜的函数式程序员呢?很不幸的,没什么可学的。我曾亲自教过一些初级开发人员用 JavaScript 进行函数式编程,他们只用大约半年的时间就变得非常擅长了。他们只需要理解一些基本概念,然后很快就能学会怎么应用它们了。终生学习的乐趣在哪里?我不会羡慕他们的。


成功是一段旅程,而不是终点

我们承认,我们的程序员是靠时间获取报酬的。就像过去两年在我家附近挖洞的建筑工人一样(顺便说一句,他们正在修建一堵墙,啊不,一条路)。

我们来定义下程序员的生产力。每个在大型企业工作过的人都知道取得成功的简单公式:

生产力 = 代码行数 x 修复bug数


修复 bug

人脑在处理状态方面真的很差,在给定的时间内,我们只能在大脑中记住大约五项工作。编程过程中的状态可以是内存中的任何数据,例如 OOP 中的字段 / 变量。使用可变状态就像是在玩杂耍。我认识的人里,没有几个能同时玩三个球的,更不用说五个了。

OOP 很好地利用了这个弱点。在 OOP 中,几乎所有的东西都是可变的。感谢上帝,OOP 非常重视开发人员的生产力问题!在 OOP 中,所有的可变状态也都能通过引用来共享!这意味着,我们不仅要考虑当前正在处理的对象的可变状态,还要考虑与之交互的其他 10-50 个对象的可变状态!这就类似于同时玩 50 个球,而且它还有一个额外的好处,那就是它能很好地锻炼大脑肌肉。

Bug?是的,最终我们会丢掉一些我们一直在玩的球。在这 50 个对象之间的交互中, 我们可能会漏掉一些小细节。但谁在乎呢,真的吗?在生产过程中,客户应该上报 Bug,任何大型企业都是这么做的。然后把这些 bug 录入到 JIRA 里,嗯,相当严肃的一款企业级软件。几年后,这些 bug 将被修复。问题解决了!

天哪,我喜欢使用我的手机银行应用程序。它非常先进,银行也很重视我的业务,他们很认真地对待我的隐私。但我被告知,这些 bug 只是一些特性!

所谓的“函数式”编程错误地隔离了状态,并使状态不可变。这有一个不幸的结果,那就是降低了复杂性,从而减少了 bug 的数量。代码库中的 bug 越少意味着我们需要修复的 bug 也就越少。承包商将无法继续向他们的客户收取修复这些 bug 的费用。任何大型企业中工作的开发人员在他们的经理眼中将会变得很糟糕,并且可能会严重影响他们在组织中晋升的机会。


代码行数

我们也应该向管理层不断展示我们取得的进步。最有效的方法是什么呢?当然是代码行数!如果我们都转向函数式编程,我们会让管理层非常不安和疑惑。“声明式”代码将使我们的代码更加简洁,代码行数也将大幅减少。实现完全相同的目标,最多可以减少 3-5 倍的代码,这是不可接受的!

换言之,面对严肃的企业管理,我们的生产力将大幅下降,我们的工作也将再次面临危险。远离“函数式”编程符合我们的最大利益。

同样的建议也适用于向客户收取工作时间费用的承包商。以下是取得成功的简单公式:

代码行数 = 编码实践 =$$$纯利润$$$

当然,这个取得成功的公式也直接适用于那些按照代码行收费的软件承包商:

if(1=='1') {
doStuff();
}else{
// pure profit
}


“意大利面”是我们的谋生之道

与函数式编程不同,OOP 为我们提供了一种一致的方式来编写意大利面代码,这对开发人员的工作效率是一个真正的福音。意大利面代码意味中更多的计费时间,它能转化成 OOP 工程师的纯利润。意大利面不仅味道鲜美,而且是 OOP 程序员的谋生之道!

面向对象对于承包商和严肃企业的员工来说都是一个真正的福音。


Bug 预防部门

我们不应该害怕使用 OOP。再说一遍,那些讨厌的 bug 没什么好担心的!任何一个严肃的企业都有一个完整的 bug 预防部门(又称客户支持部门),其主要工作是保护他们开发人员的资源免受愤怒客户的影响。毕竟,不能正确使用应用程序是客户自己的错。

开发人员不应该为诸如 bug 报告之类无关紧要的事情而烦恼。这样可以确保没有浪费任何企业资源,并且允许开发人员在使用适当的企业级面向对象抽象和复杂设计模式的同时,集中精力实现新功能。


Bug 报告过程

为了保护企业资源,通常会精心设计一个详细而严格的流程。一旦客户遇到 bug,他们通常必须在线查找客户支持的电话号码。客户将看到一个包含各种选项的高级交互式电话菜单。通常需要两到五分钟来听菜单并选择正确的选项。缺乏耐心的客户通常在这一步就放弃了。

然后,客户通常会被告知,公司正在处理“意外的大量呼叫”或“平均等待时间是 56 分钟”。他们通常会为由此带来的不便而道歉,并表达他们对客户业务的重视程度。在这个步骤中,大多数客户通常会放弃报告 bug。为了取悦顾客,通常会播放鼓舞人心的音乐。他们还会让客户去关注这款很棒的新应用程序。这款应用程序就是客户最初遇到问题的那个。

在等了 56 分钟之后,呼叫被路由到了位于北美洲某处的呼叫中心。当地的美国雇员通常都是经过严格培训的,他们能够用浓重的印度或保加利亚口音说话。代理会说,应用程序的这个问题不是他的责任,但是很高兴帮客户转交到另一个部门。

又等了 42 分钟之后,一个代理很高兴地告诉客户这个 bug 实际上是一个功能,并建议用户浏览应用程序的帮助部分。如果客户仍然坚持,代理可能会创建一个支持通知单,甚至可能会给客户回电!这个 bug 不能被复现。

我希望你现在已经确信,担心 bug 不是开发人员的工作。企业通常会采取严格的措施来保护开发人员资源。


避免新手面试的错误

如果你正在积极地寻找工作,那么花点精力把你的简历中那些“函数式”的废话都删掉吧,否则没有人会认真对待你。在现实的企业世界中,没有人受过“函数组合”、“纯函数”、“单子”或“不变”等幼稚事物的训练。你不想看起来像个局外人。谈论这些事情会让你的面试官哑口无言,而且会彻底毁掉你成功的机会。

企业的技术招聘人员也是要经过严格培训的,这使他们能够正确地区分 Java 和 JavaScript 等技术。

一定要在你的简历中穿插一些词来证明你了解各种严格的企业级抽象技术,比如类、继承、设计模式、依赖注入、实体、抽象工厂和单例等。

当被要求在白板上实现经典的冒泡(FizzBuzz)求职面试问题时,请确保你做好了充分的准备。这是你展示自己严谨的企业级系统设计能力的机会。第一步是充分设计解决方案,同时使用适当的 OOP 设计模式和严格的企业级抽象技术。很多人犯了一个新手易犯的错误,那就是依赖诸如函数之类的劣质设计技术。难怪他们从来没有收到潜在雇主的回复。


函数式编程不能用于构建严肃的软件解决方案

在考虑了上面所有严肃且严谨的论点之后,我们现在可以清楚地看到,这种所谓的“函数式”编程并没有带来任何好处。很明显,我们应该不惜一切代价避免它。

所谓的“函数式”编程是近几年来的一种流行趋势。很高兴,它正在消亡!像 Facebook 和 Microsoft 这样的大公司早就意识到了函数式编程的局限性,以及面向对象方法在代码组织中的明显优势。他们正在将资源转移到新一代面向对象语言上,即 ReasonOL 和  BosqueOOP 。这些语言将状态的可变性带到了一个全新的高度,幸运的是,它们不支持诸如不可变数据结构之类的无用的函数式的东西。


上帝的恩惠

因此,你可能会问,除了所谓的“函数式”编程,还有什么其他替代方案吗?面向对象编程,傻瓜!它是由一位真正的编程之神赐予我们的。OOP 是一种不可忽视的力量。它是开发人员生产力的终极工具,能让你和你的团队成员一直忙着工作(就业)。

愿(面向对象)的力量与你同在。还有你的代码。我是这种原力的一员。祝安好。

PS:八成大多数人已经猜到了,这是一篇讽刺性文章。所有的新开发人员,别那么严肃嘛,函数式编程非常棒!花点时间去学习它吧,你会走在大多数同行的前列。

原文 https://www.infoq.cn/article/b6gkx1crp2umU2*jIPQB

链接: https://www.fly63.com/article/detial/4782

程序员的笔记,编程写软件学到的 7 件事

如果你真的做出了一些东西,在面对那些令人眼花缭乱的理论知识,或是和你相似甚至比你做的更糟糕的人时大可不必谦虚。在一天结束之时,正是那些在战壕中的开发者——构建、测试和开发了代码的人,真正做了事情。

自学编程的六个技巧总结

这些事情可以帮助新手在他们漫长的旅程中学习编程。我知道我还有更多东西需要学习,并将继续学习如何永远地学习。最重要的事情说三遍,请继续,不要放弃,不要放弃,不要放弃。

谈谈Javascript异步代码优化

Javascript代码异步执行的场景,比如ajax的调用、定时器的使用等,在这样的场景下也经常会出现这样那样匪夷所思的bug或者糟糕的代码片段,那么处理好你的Javascript异步代码成为了异步编程至关重要的前提

编程到底难在哪里?

以买苹果为例说明程序员如何解决问题。程序员需要对问题进行透彻的分析,理清其涉及的所有细节,预测可能发生的所有意外与非意外的情况,列出解决方案的所有步骤,以及对解决方案进行尽量全面的测试。而这些正是我认为编程难的地方。

Blockly - 来自Google的可视化编程工具

Google Blockly 是一款基于Web的、开源的、可视化程序编辑器。你可以通过拖拽块的形式快速构建程序,而这些所拖拽的每个块就是组成程序的基本单元。可视化编程完成

我真是受够编程了

成为伟大的程序员,需要付出许多编程之外的努力。我们的大脑是有限的,每天要应付的问题复杂到足以让人精神崩溃。当工作不顺利时,多少都会有些冒名顶替症候群的感觉。

前端的编程软件哪些比较好用?

推荐8款最好用的前端开发工具供美工或者前端开发人员使用,当然若你是NB的全栈工程师也可以下载使用。Web前端开发最常见的编程软件有以下几种: 在前端开发中,有一个非常好用的工具,Visual Studio Code,简称VS code

如何保持学习编程的动力

学编程现在看起来挺简单,因为网上有丰富的各种资源。然而当你实际去学的时候就发现,还是很难!对我来说也一样。但从某天起,我决定认认真真学编程一年。后来又过了一年,又过了一年又一年……我好像有点感悟。

编程小技巧

命名最好遵循驼峰法和下划线法,并且要清楚的表达变量的意思。相对于驼峰法而言,我更喜欢下划线法。下划线法可以更清楚的看出这个变量表示的意思。比如aBigGreenBanana和一个a_big_green_banana。

CSS并不是真正的编程语言

每隔几个月就会出现一篇文章表明:CSS并不是真正的编程语言。以编程语言的标准来说,CSS过于困难。使用这门语言会很有创造性:事实确实如此,CSS不同于传统的编程,且具有缺陷,同任何标准化编程语言相比

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!