为何,以及如何提炼一个函数

什么是函数

Function: [countable](computing) part of a program, etc. that performs a basic operation

program:a set of instructions in code that control the operations or functions of a computer

instructions:[countable] a piece of information that tells a computer to perform a particular operation

如上可得出,函数为程序的一部分,程序由一系列指令组成,指令用于完成某种操作

那么,是否可以认为,函数封装了某种操作?

操作描述的是一个过程,大部分我们期望的是如下的一个过程:

输入 -> 逻辑 -> 预期结果

那么,⭐️为何要封装操作呢?这貌似也回答了为什么要提炼函数?

  • 良好的代码组织
  • 逻辑复用

那么,应该以怎样的方式进行组织呢?

给这段操作起一个名称,通过调用这个名称,来达到组织代码的目的。同时,如果重复调用该名称,是否能重复执行里面的操作呢?

于是,通过调用相同的函数名称,可以实现逻辑复用

既然是逻辑复用,那么通过函数与函数调用,能否执行某种循环操作呢?答案为是,如后面的【循环替换】

单个函数操作的数据范围是有限,于是,通过函数参数,使其能与外界进行交互。

Extract Funtion

extract - 牛津高阶词典
extract (from something) a short passage from a book, piece of music, etc. that gives you an idea of what the whole thing is like

可以看出,你对extractpart有着初步的认识(an idea)

Motivation 动机

why do we extract function?

代码组件及逻辑的复用

如果提炼出来的part只在一处地方使用,还有必要吗?

视情况,如果函数意图与实现相差太大,可以考虑提炼。

函数提炼的原因,另一个合理的观点是:将意图与实现分离(separation between intention and
implementation

intention:意图,通常指你内心的想法。(我只需要了解它能干什么,而不关心是如何实现的)

一旦接受这个原则,你甚至会写出仅仅只有一行的代码

比如:在一些黑白计算机的系统中,调用reverse 函数使得某些文字或图片高亮显示,我将其封装到highlight函数中,可以看到,highlight竟比reverse 还长,但是,这并不重要。因为,reverse的的意图(intention)与实现之间有着很大的差距(big
distance)


提炼一定是好的吗?

不是,不合理的组织代码,会使得代码变得非常乱。所以需要⭐️视情况进行合理的提炼

Mechanics 做法

1️⃣ look at a fragment of code, understand what it is doing, then extract it into its own function named after its purpose.

purpose: 事情应该被这样实现,一种规范

查看某个代码片段,理解其行为,然后将其提炼到一个独立的函数,并以这段代码的用途为该函数命名。

函数名称的作用:1️⃣ 将 fragment of code fit together - effectively 2️⃣ 了解这些 fragment of codeintention

关于函数命名的技巧:在函数上方写一段注释,描述这个函数的用途,再将这段注释变为函数的名字。

❗️ 如果想不出一个好的名字,可能你不应该提炼这块代码

函数,通常表示一种行为,其前缀最好为动词;变量,通常表示一种状态,其前缀最好为形容词。

2️⃣ 查看extracted code中是否存在访问不到的变量,如果是,在 source function通过参数传递

如果变量只在extracted code中使用,就把变量的的声明也搬到extracted code

Demo 例子

提取重复代码

同一个类两个函数有着相同的表达式,通过extract function提炼出重复的代码,并且 invoke the code from both places

class SimpleCalcultor {
  logNumberAddOne(aNumber) {
    const manyDashes = '--------------'
    console.log(manyDashes)
    console.log(aNumber + 1)
    console.log(manyDashes)
  }
  logNumberAddTen(aNumber) {
    const manyDashes = '--------------'
    console.log(manyDashes)
    console.log(aNumber + 10)
    console.log(manyDashes)
  }
}

after extract function

class SimpleCalcultor {
  logNumberAddOne(aNumber) {
    this.logNumber(aNumber + 1)
  }
  logNumberAddTen(aNumber) {
    this.logNumber(aNumber + 10)
  }
  logNumber(aNumber) {
    const manyDashes = '--------------'		// <------	该变量只在extracted code中使用,就把该变量的的声明也搬到extracted code中
    console.log(manyDashes)
    console.log(aNumber)
    console.log(manyDashes)
  }
}
将返回值赋值给局部变量

如果在extracted code中修改了source code的变量,则在extracted code中将修改的值返回出去即可

function foo(arr) {
  let sum = 123		// <------ 在 extracted code 中修改了 source code 的变量
  arr.forEach(e => sum += e)
  console.log('do some other things--------')
}

foo([1, 2, 3, 4, 5])

after extract function

function foo(arr) {
  let sum = getSum()		
  console.log(sum)
  console.log('do some other things--------')

  function getSum() {
    let res = 123
    arr.forEach(e => res += e)
    return res		// <------ 将修改的值返回出去
  }
}

如果自身语言支持嵌套函数,应首先extract成嵌套函数,以防止函数名重名等问题

个人风格:将函数的返回值命名为 res,这样一眼就可以知道它的作用

函数式语言

《JavaScript 语言精髓与编程实战》(第三版)第五章:JavaScript 的函数式语言特性

根据冯诺依曼体系:运算数要先放到寄存器中,然后再参与CPU运算,对存储的理解规范了我们的代码风格

如果说运算式语言形式类似如下

const a = 1
const b = 1
const c = 1

const res1 = a + b
const res2 = res1 + c

console.log(res1)
console.log(res2)

运算的目的是为了“产生值”

而函数式语言——为了自身的某些特性,而产生了一种复杂的代码风格(在 JavaScript 中,函数可被当作参数返回值使用)

函数式语言形式类似如下:以连续运算为核心特征

const a = 1
const b = 1
const c = 1

function sumAndLog(a, b) {
  const res = a + b		// <------ 根据输入,得到某个结果产生值
  console.log(res)		// <------ 执行某种副作用
  return res			// <------ 返回可供后续运算的值
}

sumAndLog(sumAndLog(a, b), c)		// <------ 通过连续的表达式运算, 得到最终的结果

可以看到,函数式语言舍弃了保留中间结果的过程,而是直接将输出的结果当作下一个运算的输入

你可以将函数当成一个运算符,如“+”

1 + 1 + 1
sum(sum(1, 1), 1)

逗号运算符与连续运算

逗号运算符表明从左至右计算两个操作数,并返回右操作的值

function foo() {
  console.log('1')
  // 左边可以进行某些操作, 如打印
  return console.log('2'), 3
}
console.log(foo())  // 依次输出 1 2 3

分支、循环替换

一般编程语言会有:顺序,分支,循环三种基本逻辑结构。它即可用于组织语句,又可用于陈述逻辑

然而,分支循环是可以被三元表达式函数递归替代的。

分支替换
const a = 123
if (a > 0) {
  console.log('a > 0')
} else if (a == 0) {
  console.log('a = 0')
} else {
  console.log('a < 0')
}

// 替代如下 
(a > 0) ? console.log('a > 0')		// <----- 第三个操作数:为下一行运算的返回结果
  : (a == 0) ? console.log('a = 0')
  : console.log('a < 0')

连续运算过程中,将输出的结果当作下一个运算的输入,因此,无需中间变量来“寄存”,只需要值声明

循环替换
let res = 0
for (let i = 0; i <= 10; i++) {
  res += i
}
console.log(res)

// 替换如下
function getSum(i) {
  return (i > 0) ? i + getSum(--i) : 0
}
console.log(getSum(10))

函数模拟循环会产生“栈溢出”的问题

而 JavaScript 引擎会进行尾递归优化,当满足:外部函数的返回值是内部函数的返回值时,如

function outerFunction() {
  return innerFunction()  // <----- innerFunction 的返回值也是 outerFunction 的返回值
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值