5. 函数式编程
5.0、函数式编程
Go语言作为一个通用型语言,它对函数式编程主要体现在闭包上面。
1、函数式编程 VS 函数指针
函数是一等公民:参数、变量、返回值都可以是函数,在别的语言中大多不是这样的,比如在C++里面只有函数指针,在Java里面我们只能调用,不能把函数传给别人。
高阶函数:参数可以是函数,1.6.3里面的apply函数就是一个高阶函数。
函数 → 闭包:首先用个例子来了解一下闭包的用法
结果输出为
上述的 v 就称为局部变量, sum 称为自由变量, 称为函数体,整个就叫做一个闭包。用一张图来概括就是:
2、“正统”函数式编程
不可变性:不能有状态,只有常量和函数;当然这和平常的函数不一样,连变量都没有,甚至连选择语句、循环语句都没有。
函数只能有一个参数
要是上面的累加想做一个稍微正统函数怎么做呢?
当然正统的不一定是最好的,正统式的写法经常导致代码的可读性变得不是很好。
5.1、函数式编程实例演示
1、斐波那契数列
Go语言的官方案列中,对闭包的讲解通过一个常见的例子:斐波那契数列,为了更好的理解闭包的感念,那这里我们就将这个例子再来演示一遍
比如我们要打印这一串斐波那契数列,我们就需要不停的调用上面的斐波那契数列的生成器。
2、为函数实现接口
这个斐波那契数列的调用的生成器跟文件有点像,我们可以把它包装成一个 这样就跟打印一个文件一样生成这个斐波那契数列。
首先我们先定义我们的类型 ,就取名Generate好了
同时需要将 函数的类型改掉
是一个类型就可以实现接口,这就是Go语言灵活的地方,下一步我们实现这个Reader接口
这里我们会发现函数也可以实现接口,这就是Go语言的不一样的地方,因为函数是一等公民,它既可以作为参数,也可以作为接收者。首先我们要先取到下一个元素 ,然后将下一个元素写进p。然后我们直接用一个写好的文件打印的函数
最后我们就可以直接调用了
当然,上述的代码是存在瑕疵的,比如这个 函数会一直读下去,就变成一个死循环了,我们需要设置其终止条件。比如上面的p太小的话,只读了一半,当然这边就留给读者后期拓展了。
6. 错误处理和资源管理
我们实际的代码不止 ,我们的代码是要运行在服务器上的,要和各种各样的用户进行交互,所以我们这里就要了解一下Go语言的资源管理和出错处理。
6.0、defer调用
1、确保在函数结束时调用
比如一个常见的代码
我要是想要让1在2后面输出该如何做呢?你说调换一下顺序呗,道理我都懂,但是我们今天要介绍的不是这个,我们只需要在打印1之前加一个defer就可以了
要是有多个defer呢?它的输出顺序又是什么样的呢?
上面这段代码,输出的结果又是什么?
这里我们可以发现defer的调用实际是一个栈,先进后出。当然defer的最大的好处是什么呢?就是当程序中间有return返回甚至panic的时候,依然不影响 defer 后面的代码的执行。
上述的代码在panic之后,1 2 依然能够正常输出。
2、场景演示
当然说了这么多,我们在代码中常见的使用defer的场景有哪些呢?比如我们创建文件,写文件这些,过去我们用别的语言经常会在处理文件的最后释放句柄,因为中间隔了很多的文件操作,经常可能会忘记释放句柄。那Go语言就针对这样的场景做了非常好的优化,通过defer关键字实现,下面我们就通过一个简单的写文件事例来演示一下:
一个完整的事例就演示到这边,比如常见的 Open/Close、Lock/Unlock这些成对出现的都可以使用defer
6.1、错误处理概念
因为在我们实际的程序中,有错直接panic中断程序执行,这时非常不友好的,通常我们会对其出错处理。比如上面的事例中 函数返回的 不为 的时候,我们需要做一个出错处理,
我们可以直接打印出相关的错误信息,然后直接返回。这就是常见的错误处理方式之一,当然在函数内部也可以将错误信息直接作为结果返回。
6.2、panic和recover
1、panic
停止当前函数执行
panic和我们其他语言的throw exception很像
一直向上返回,执行每一层的defer
当然相对还是友好的,每层的defer还是会用到,一层一层的返回,返回到最后程序就会自动退出了
如果没有遇见recover,程序退出
2、recover
仅在defer调用中使用
获取panic的值
如果无法处理,可充新panic
主要的特性就可以用上述几句话概括,为了更好的理解上述的概念,下面用一个简短的代码来学以致用
上面就是一个 panic 和 defer 的结合使用,他的输出结果会是什么样的呢?
从上述输出结果我们可以看到panic的前两个特性,那结合recover又会是什么样的呢?
从上面我们可以看到 recover 是一个interface, 所以在判断的时候需要判断 r 是否是一个 error,结果自然会是输出
那我们再用一个实际一点的例子来测试一下,比如除数为0的例子
结果输出
上面的两个例子简单介绍了panic、recover的基本使用,下面通过一个稍微实际一点的例子来综合讲述一下一般的项目中是如何统一处理错误的。
6.3、服务器统一出错处理
现在呢我们就通过一个Http服务来展开如何统一处理服务器出错这件事,结合一个实际读取目录内文件的例子来简单介绍一下
因为在GOPATH下有一个 文件,浏览器输入一下地址 ,浏览器正确输出结果
万一我访问一个不存在的文件呢?会得到什么样的结果,比如我现在访问 GOPATH目录下没有demo.txts文件,自然你会想到会panic一个错误
从上面的部分的报错信息来看,
相关的错误信息都是 的 函数报出的,具体是哪一步报出的我就不细说了,有兴趣的可以自己按照例子自己查阅相关的源码,说到这那错误统一处理又是如何处理呢?
我们先把第一个panic替换成
我们再来访问上述地址
相比之前,提示稍微友好一点了,但是这对用户来讲还是不合适的,直接将程序内部错误信息输出给用户有些欠妥。我们可以包装成一个外部的Error,首先我们先定义一个函数appHandler, 返回一个error
然后定义一个 errWrapper 函数, 返回一个handler 里面需要的函数
然后将writer和request传进handler,通过switch判断err的类型,做一个统一的返回处理;这时我们需要将原来的业务逻辑的代码稍微做一下调整,
的第二个参数我们需要改为 同时将原来的函数作为参数传进去,当然这个函数为了代码的可读性应该单独抽离出来,相应的返回直接返回error就可以了,这时候我们再去访问之前的一个不存在的URL
这时候的错误就明显友好了很多,讲到这就是一个简单的统一错误处理的思路。
7. 测试和性能调优
7.0、测试1、传统测试 VS 表格驱动测试
测试的作用对于一个软件行业从业者而言都是毋庸置疑的,Go语言在测试这块它有自己独特的见解,下面我们先介绍一下这两种模式下的测试
传统测试
测试数据和测试逻辑混在一起
出错信息不明确
一旦一个数据出错测试全部结束
下面我们简单的举个例子:
很明显上面的几个特征它都占了,那下面我们来看一段Go语言的测试case
上述就是一个典型的表格驱动测试
表格驱动测试
分离测试数据和测试逻辑
明确的出错信息
可以部分失败
Go语言的语法使得我们更容易使用表格驱动测试的测试模式
2、实例演示
说了这么多,我们通常又是如何写测试用例呢?首先下面是一段加法的代码
现在就写上面的函数的测试用例
用IDE的同学直接点击 就可以了,当然也同样支持命令行运行,进入到指定的文件目录下面
运行相关的执行命令就可以了,要是有错误的case依然不影响相关的测试的执行,比如:
测试用例的执行结果为
我们需要将不符合预期的case做出检查,看是否是代码逻辑有问题,还是case的问题,这就是一个完整的测试用例的编写的过程。
7.1、代码覆盖率和性能测试
1、代码覆盖率
用IDE的同学我们会发现点击 按钮的时候还有一个 with coverage 的选项
这就是一个测试用例的代码覆盖率的结果。
IDE这块有详细的覆盖率报告,可以看到左侧的绿色就是代码的覆盖的范围,右侧有详细的每个文件的覆盖率。当然除了IDE之外命令行也是同样支持的
直接查看这个 a.out 文件,似乎看得不是很明白,当然我们有一个工具叫
运行上面的命令,就会展现一个下面的静态页面
这就是一个详细的覆盖率报告
2、性能测试
对于程序员而言,代码的性能是每个人都会去关注的,Go语言在性能测试这块依然有它的独特见解
上面就是一段性能测试代码,我们不需要关注这段代码具体要跑多少次,Go语言自身会帮你决定,IDE点击 完,输出相关的结果
总共跑了多少次以及每次的平均耗时,都会给出结果。当然同样支持命令行的交互方式
7.2、使用pprof进行性能调优
上面我们刚提到了性能测试,下一步自然就是我们该如何优化代码的性能,这里我们需要介绍一下Go语言的性能分析工具 ,就依然用上面的这个例子进行阐述它的基本用法,我们要是想了解一段代码具体它慢在哪里,首先呢我们先让它生成一个cpuprofile
这时候我们发现现在多了一个 文件
查看之后你会发现是一个二进制文件,那我们该如何处理呢?Go语言的 就要登场了
这时候出现了一个交互式的命令行,我们可以通过输入 help 得到相关的使用说明
我们这里就介绍一个最简单的方式,敲入web回车,z这里做一个温馨提示
出现上述报错的,是因为Graphviz没有安装,安装好了之后再敲入web会生成一个SVG文件,用浏览器打开它
一张图可以很明显的表现出哪边花的时间多哪边花的时间少,当然也可以从框框的大小来做判断,我们需要优化比较大的框框的部分。上述的代码因为太过于简单,大家可以试着用自己写的代码进行性能分析。
7.3、生成文档和事例代码
在我们实际的开发过程中,文档的重要性不必多说,服务调用方、协同开发的小伙伴、QA都需要文档;其他的语言我们经常需要依赖其他的文档工具,比如:ApiDoc、doxmate、daux等等。
首先我们先介绍一下 的常规的用法
除此之外呢,我们可以通过help来查看
再比如我们可以查看系统的文档
当然我们最常用的命令是 ,我们help看一下它的基本用法
我们看到有个http的用法,现在我们试一下
打开浏览器,输入 http://localhost:6060
完整的Web版的Go语言的文档就可以使用了。当然不单单包含系统函数,同时还包含我们自己写的函数的文档,现在我们就演示一下
我们在函数前面加上了注释,这是我们重新启动 我们会发现
相关的注释已经加上了。Go语言除此之外还提供了写示例代码的方法
直接添加一个 函数,还是像之前一样写代码,最后我们要写一个 的注释,那你现在是否有疑问,下面的 1 是什么意思?这里说下,这是我随便写的,这时候 这段代码
我们再把正确的输出贴到上面的输出中,重启godoc
这时候完整的示例代码就已经生成到文档中了。
领取专属 10元无门槛券
私享最新 技术干货