8

我在函数式编程上犯下的几个错误 | 程序师 - 程序员、编程语言、软件开发、编程技术

 1 year ago
source link: https://www.techug.com/post/several-mistakes-i-made-in-functional-programmingb4e163e36c8b3265ca53/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

【CSDN 编者按】提到编程思想,你首先想到的会是面向对象还是面向函数编程呢?本文作者分享了自己在函数式编程实践中踩过的一些坑,分享给大家,希望能对你有所帮助。

原文链接:https://robertwpearce.com/how-to-lose-functional-programming-at-work.html

未经授权,禁止转载!

作者 | Robert Pearce    译者 | 弯月

出品 | CSDN(ID:CSDNnews)

在过去几年里,我在使用函数式编程开发拥有大量 JavaScript 代码的应用程序时,犯了不少错误,也走了一些弯路,再此复盘并分享给大家。

img1678068277455029792.jpeg

不使用静态类型检查

不使用如下工具:

  • TypeScript

  • ReasonML

const processData = composeP(syncWithBackend, cleansePII, validateData)// * What arguments and their types are expected here?//// * If each function is written like this, how can//   one suss out what data are flowing where?//// * How hard is this going to be to debug?//   Use this everywhere: `(x) => (console.log(x), x)`

这些参数的类型应该是什么?如果每个函数都这样写,谁能猜出它们传递的是什么数据?这段代码的调试会非常困难。

或许你认为,这种““Point-free or die””的编程风格才是真正的问题。那么来看看这两种写法:

async function processData(data) {  await validateData(data)  const cleansedData = cleansePII(data)  await syncWithBackend(cleansedData)  return data}

或者使用Promise链:

// or for the Promise-chainers…const processData = data =>  validateData(data)    .then(cleansePII)    .then(syncWithBackend)    .then(() => data)

无论哪种写法,想想三个月之后还有谁能明白它们是做什么的。

不使用众所周知的代码文档工具

比如 jsdoc。这些工具会加剧其他团队成员理解代码的难度,同时还无法使用自动补齐。

下面是一个例子,注意其中的文档标签并不是jsdoc的标准标签:

// NOTE: this is an untested, small example/** * @typedef {Object} ReportingInfo * @property {("light"|"dark")} userTheme - Current user's preferred theme * @property {string} userName - Current user's name * @property {UUID} postId - The current post's ID *//** * Validates that the reporting data (current user site prefences and post info) * is OK, removes personally identifiable information, syncs this info with the * backend, and gives us back the original data. * * @param {ReportingInfo} data - The current user's site preferences and post info * @returns {Promise<ReportingInfo>} - The original reporting data */const processData = data => // …

不认真培训新老同事

不要以为写几篇文章、找一些学习资源,交给刚接触函数式编程的人,鼓励他们问问题,就能有好结果。

也不要走向另一个极端:把所有的时间精力都花在几个人身上,忽略其他人,不记录学习经验,还不鼓励他们互相帮助。

不必麻烦其它工程团队加入,也无需所有人朝同一个方向努力

“我构建出来,他们就会注意到……不是吗?”

利用午餐时间学习函数编程?不行,万一他们发现我什么都不懂,怎么办?

与其他团队领导会面,问问他们是否对函数编程感兴趣,看看他们还有什么更好的方法,或者听听他们不感兴趣的原因?不行,这事儿得向经理报告,他们会认为我太天真或太能折腾,那我岂不是得不偿失?

将自己所学的技术私藏起来,其他团队无法做出贡献,也无法改善现状。

宁可使用错误的抽象,也不采用错误的重复

在 2014 年美国芝加哥举行的 RailsConf 大会上,Sandi Metz 发表的演讲(https://www.youtube.com/watch?v=8bZh5LMaSmE&ab_channel=Confreaks)给我留下了深刻的印象,她认为“即使不得不重复,也不要使用错误的抽象”。两年后,她在博客上发表了一些很棒的评论“错误的抽象”(https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction)。

她认为,提取核心的业务逻辑,反复抽象为广泛的概念,最后将导致没有人能充分理解这些概念,更没有人了解这些抽象如何工作。

如果没人能理解这些抽象,PR的审核也就变成了单纯的点赞,那么很快就会人员流失了。

不重构不适合团队的旧代码

旧代码的唯一用途,就是告诉刚刚加入团队的成员,你们当年的代码是多么不堪。这些代码早就该被重写,但很多人把时间和精力都放在了编写新功能上。

用 compose 连接所有 API 调用,加大调试难度

这段代码看上去似乎还不错:

// handler for POST /postsimport { createPost } from 'app/db/posts'import { authenticateUser, authorizeUser } from 'app/lib/auth'import { trackEvent } from 'app/lib/tracking'const validateRequestSchema = payload => { /* … */ }export const handleCreatePost = curry(metadata =>  pipeP(    authenticateUser(metadata),    authorizeUser(metadata),    validateRequestSchema,    createPost(metadata),    tapP(trackEvent('post:create', metadata)),    pick([ 'id', 'authorId', 'title' ])  ))

但是你能发现这段代码需要两个参数吗?你知道 authenticateUser 会忽略第二个参数吗?怎么知道?trackEvent 呢?它接受 payload 吗?或者 createPost() 会返回跟帖子有关的数据?

改成这样就好多了:

export async function handleCreatePost(metadata, payload) {  await authenticateUser(metadata)  await authorizeUser(metadata, payload)  await validateRequestSchema(payload)  const post = await createPost(metadata, payload)  await trackEvent('post:create', metadata, payload)  return {    id: post.id,    authorId: post.authorId,    title: post.title,  }}

我并不是说第二种写法比第一种写法好,但第一种写法确实会让人摸不着头脑。

重新发明过程式编程,并称之为“声明式”

const setBookReadPercentByType = (contentType, statusObject) =>  assoc(    'readPercent',    pipe(      prop('subItems'),      values,      filter(propEq(contentType, 'chapter')),      length,      flip(divide)(compose(length, keys, prop('subItems'))(statusObject)),      multiply(100),      Math.round    )(statusObject),    statusObject  )

使用大量函数组合的模式

比如下面就是四种函数组合模式,每种还可以写成Promise版本,再加上它们之间的各种组合,这还没提到使用pipeWith、composeWith等。

// 👇 These 4, plus Promisified versions of them,//    plus combinations of them all used at once;//    doesn't include ramda's pipeWith and composeWith// composeconst getHighScorers =  compose(    mapProp('name'),    takeN(3),    descBy('score')  )// pipeconst getHighScorers =  pipe(    descBy('score'),    takeN(3),    mapProp('name')  )// composeWithValueconst getHighScorers = players =>  composeWithValue(    mapProp('name'),    takeN(3),    descBy('score'),    players  )// pipeWithValueconst getHighScorers = players =>  pipeWithValue(    players,    descBy('score'),    takeN(3),    mapProp('name')  )// …but then now mix and match them with actual,// real-life business logic.

代码里写满晦涩难懂的代数操作符

在代码里使用这些代数操作符,肯定会让你的队友一头雾水:

  • Task, Maybe, Either, Result, Pair, State

  • bimap

  • chain

  • bichain

  • option

  • coalesce

  • sequence

  • map — 我指的不是 Array.prototype.map, 也不是 new Map(), 也不是键值对象的那个map

自己转换数据,而不是让SQL完成

本来 SQL 非常适合转换数据,但非要以“不可变数据”为名,建立数据流水线,在流水线中逐步转换,用这种方式来尽可能能消耗内存。

在同事的 PR 中建议遵循你的函数式风格

“这段代码非常好,要是把所有参数都反过来、删掉中间变量,然后将这些操作都映射到Either上怎样?”

“我注意到你在函数中明确地构造了这些对象。要是能使用<某个函数>,就能定义输出对象的形状,然后把函数作为值来查找或计算每个值。”

还有一些…

  • 分享一些初学者看不懂的函数式编程的文章,让他们觉得自己很差劲;

  • 持续用函数式编写代码,虽然整个团队里没有第二个人这么做;

  • 消极地使用表情符号给别人的PR写评论;

  • 在公司发表“函数式编程”的演讲,把你的错误传递到整个公司。

总结

上面提到的许多错误表面上是经验不足、缺乏技术领导力造成的。但我认为,真实原因应该更深。

最后,我们不能抛弃函数式编程的核心原则:

  • 不变性:重新创建对象,而非修改原来的对象,以获得更好的性能;

  • 纯函数:使用相同的参数调用相同的函数,得到相同的结果;

  • 将副作用放到程序的逻辑边缘上;

  • 类很少,没有继承,没有map/filter/reduce 等。

本文文字及图片出自 CSDN


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK