news 2026/7/4 6:58:06

提高代码质量系列之三:我是怎么设计函数的?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
提高代码质量系列之三:我是怎么设计函数的?

这篇其实是上两篇的两个主题思想的承接和发散:

    1. 我也想少写注释,想用2-4个很清晰的单词去描述函数,但是这个函数好复杂啊,我恨不得写近百字去描述它,要我用几个单词去描述?臣妾实在是做不到啊~ <如何做到少写注释>
    2. 我也不想写这么多if else,然后看着那一堆一堆{}{{}{}{{}}}}}}}{{{}{{}头晕眼花,但逻辑就是有这么复杂,我能怎么办呢? <如何简化代码逻辑>

这篇博文,应该就是我对于以上问题结合设计原理的一些思考,不算多高深,但都是自己的总结,我也不会去谈xx设计模式,因为我觉得设计模式的本质就是让你写更好的代码,而不是反之,所以理解它背后的思想,才是真正有价值的东西.

  • 尽可能让你的函数符合"纯函数"标准

先介绍下什么是"纯函数" 纯函数其实并没有一个很统一的定义,像Haskell的定义,就太苛刻,几乎是数学领域了,我比较认同下面这个定义:

纯函数应该具有以下两个特性:

    • 它没有任何副作用。 函数不会更改函数以外的任何变量或任何类型的数据。

    • 它具有一致性。 在提供同一组输入数据的情况下,它将始终返回相同的输出值。

我自己总结下,意思是一个设计良好的函数,应该就像一个黑盒子一样,你完全不需要关注函数内部的实现,你只需要关注三点, 1.函数名 2.函数接受的参数类型 3.函数返回值的类型,只要我们确定了这三 点,我们即可完全"掌控"这个函数, 我们给定一个输出,必然会返回预设的结果,这个结果不受其他任何因素的干扰. 当然,这其实是最理想的情况,"纯函数"也并非就是非黑即白的定性修饰,它更多的是一个程度上的修饰,有些函数是无论如何也不可能写成成纯函数的,比如访问非托管资源的函数. 但我们可以这样说:FunA和FunB都不是纯函数,但FunA比FunB更"纯函数"(可以类比"声明式"这个概念).

, 更具体的介绍,可以看msdn里面的一个小专题 纯函数转换简介 .

那么,我们为什么要写纯函数呢?因为省事省心, 直接来两段代码,

public void DoSthWithTwoVariable1() { var p1 = Session["P1_key"]; var p2 = _p2; //......DosthWith p1 and p2 } public void DoSthWithTwoVariable2(Type1 p1 , Type2 p2) { //......DosthWith p1 and p2 }

第一个函数要考虑的东西很多,比如session里面是否有值,-p2这个全局变量会不会受到其他地方的干扰,而这些其实不该是doSth应该关心的,它的职责范围被扩大了.

这两个函数,其他人或者过段时间我们自己调用的时候,谁更让人放心?

所以我们要使函数显得純.第一步就是尽可能避免全局变量,我们分析一个函数,就只分析这个函数的全部代码(有效范围)就好,如果引入了全局变量,我们分析的时候,关注范围也难免会被强制扩大到全局,同理,能声明为静态函数的,就应该避免声明为成员函数,因为成员函数可以访问对象的实例,而该对象在调用成员函数的时候,是个什么状态,有无初始化,函数是否会修改实例(引用类型)的参数,如果我们要对这个函数做重构,就难免会束手束脚.

宁愿多花一点功夫,将需要的变量在封装的纯函数中不断传递,也不要轻易将它设置为全局变量,因为在函数中传递,按照你调用的顺序,它的流程仍然是稳定的,而一旦使用全局变量,那么它就失去的约束,在哪里被人初始化了?怎么初始化的,顺序是不是按我要求的,有没有哪个地方在我做第二次初始化之前,就调用了第二次处理的功能逻辑?

再看一个例子:

public void SetType3() { var p1 = this._p1; var p2 = this._p2; //......Deal p1 and p2 this._p3 = xxx; } public static void SetType3(MyClass obj) //静态函数,但修改了实例的成员 不是纯函数 { var p1 = obj._p1; var p2 = obj._p2; //......Deal p1 and p2 obj._p3 = xxx; } public static void SetType3(Type1 p1, Type2 p2, MyClass obj) //静态函数,但修改了实例的成员 不是纯函数 { //......Deal p1 and p2 obj._p3 = xxx; } public static Type3 GetType3(Type1 p1, Type2 p2) { //......Deal p1 and p2 Type3 p3 = xxx; return p3; }

以上四个函数的纯函数程度,是依次递增的,都是大家很常用的写法,那么这四个函数的区别是什么呢?

是我们调用者对函数内部实现逻辑的关注程度,依次递减,他们的功能也越来越纯粹(意味着更容易提炼和复用),调用起来也更省心,

当然,也难免会更琐碎,比如GetType3,还需要做一些具体的取值,传值,赋值操作.

其实他们也没有什么优劣之分,这之间的度,自己把握就好.

Ps: 2015-01-29 11:06:30补充:

今天看Qunit官网介绍,发现里面一个例子,也是我这种思想的一个印证: Make Things Testable

function prettyDate(time) VS function prettyDate(now, time) //前者内部声明了now(当前时间),后者作为参数传递进去。

很明显,qunit官方也是推荐这种纯函数式的风格的。

Ps: 2016-2-15 11:27:59补充:

今天学习React的时候,看见他们对Component里面方法的分类,同样有这种思想的体现,

Methods defined within this block arestatic, meaning that you can run them before any component instances are created, and the methods do not have access to the props or state of your components. If you want to check the value of props in a static method, have the caller pass in the props as an argument to the static method.

通过对函数的合理分工,让整个组件的逻辑更加清晰工整.

我现在越来越觉得,我们最需要提高的,不是编程语言,功能库的熟练程度,而是我们对"美"的感知能力,当一个事物被精心雕琢,融入了作者的智慧与心血时,它自然而然就会散发一种美的光芒.

  • 函数内部的变量,尽可能少,声明尽可能晚,绝对禁止一值多用

变量尽可能少:函数内部的变量,有效范围是整个函数,如果我们在函数前面声明了10个变量,那么我们都必须时刻关注这些变量的使用情况,有些变量其实就在前面用了一次,但后来阅读的时候,你也不记得后面是不是还用到了它,所以减少变量数量,就意味着减少代码复杂度.举例:

//取得操作实例,根据id取得对象,取出最终我们要的state, // appointmentManager,thisAppointment这两个变量我们都只用了一次,但以后看的时候,我们也不确定后面还用不用 var appointmentManager = ManagerFactory.Create<AppointmentManager>(); var thisAppointment = appointmentManager.GetById(appId); var state = thisAppointment.State; //其实可以这样,那么我们只需要关注一个state就好,阅读压力大大减少 var state = ManagerFactory.Create<AppointmentManager>().GetById(appId).State;

声明尽可能晚:可能我们写类的时候养成了习惯,将变量放在最上面,统一声明,易于整理和查阅. 其实类的声明和函数的声明是不一样的,类的所有成员(变量和函数)都是无所谓先后的,而函数里面的局部变量,则是有先后顺序的,我们在不必要的地方引入了不必要的约束,也就意味着不必要的麻烦.

比如我们有一个200行代码的函数,我们在最前面声明了10个变量,这些变量是依次在函数不同部位使用的,但因为在最前面已经声明了,所以我们阅读这个函数的时候,也需要时刻注意这10个变量在函数中的使用情况, 这里我们简单的引入一个"关注度"的概念: G = 变量个数*变量的有效代码范围 ,那么这时候的总G数 = 10*200 = 2000.

而如果开始只声明2个变量,剩下的变量在使用的时候才声明,比如p3,p4是在101行代码里面声明的,那么你阅读1-100行代码的时候,就不需要关注p3,p4了(也没法关注,都还没声明呢),然后剩下6个变量在151行声明,那么现在的关注度,就只有G=2*200 +2*100 +6*50 = 900.

禁止一值多用:前面不是说要尽可能少的声明变量么,有些人就这样做:比如我声明一个state,表示Appointment的状态,用完之后,后面需要用订单状态的时候,我仍然用state字段去接值,参与新的,属于Order的业务逻辑,这个我还真见过.不过相信这种大神应该还是极少数吧.

  • 重构还是不重构,这不是个问题

几乎所有提到程序设计的书籍,都是推荐将函数中比较独立的业务抽取出来,放在一个新的函数中,好处很多:结构清晰,代码复用,业务解耦合.

但有时候我们的情况很尴尬,说功能独立吧,也不是特别独立,说要提公吧,其实在其他地方用的可能性也不大,但要就这样和主体业务放在一起,代码也确实显得比较乱,提公之后,又将业务逻辑分散了,这种情况应该怎么办呢?

其实我们可以选一个折中的方案:委托.

比如一个流程,需要在保存之前筛选初始数据,这个筛选的方法很大可能只在这里用(但也不排除以后再其他地方也会用,虽然可能性不大),和主体业务耦合也比较强,其实我们可以在函数中声明一个

Func<IList<Product>, AttrItemDTO, bool> FilterProduct1= (lambda Express) 或Func<IList<Product>, AttrItemDTO,int, bool> FilterProduct2= (lambda Express)

我们可以通过传递参数的形式,写成纯函数形式的FilterProduct2(第三个参数就是state),也可以写成FilterProduct1,在lambda里面直接使用前面函数中声明的"全局变量"state,

这两者都是将筛选这一流程进行了一次折中的"重构",而且花销很小, 首先它的业务逻辑还是线性顺序进行的,一条线下来,再次即使以后需要重构或者提公,也非常容易.

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/4 6:57:34

Typical架构解析:从Schema到代码生成的完整工作流

Typical架构解析&#xff1a;从Schema到代码生成的完整工作流 【免费下载链接】typical Data interchange with algebraic data types. 项目地址: https://gitcode.com/gh_mirrors/ty/typical Typical是一个专注于代数数据类型的数据交换项目&#xff0c;它提供了从Sche…

作者头像 李华
网站建设 2026/7/4 6:57:15

nwpu-cram之量子通信:原理与协议终极指南

nwpu-cram之量子通信&#xff1a;原理与协议终极指南 【免费下载链接】nwpu-cram 西北工业大学/西工大/nwpu/npu软件学院复习(突击)资料&#xff01;&#xff01; 项目地址: https://gitcode.com/GitHub_Trending/nw/nwpu-cram 量子通信作为未来信息安全的重要技术&…

作者头像 李华
网站建设 2026/7/4 6:56:58

CANN白盒设计网络搜索

Task C&#xff1a;网络搜索 【免费下载链接】cannbot-skills CANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体&#xff0c;本仓库为其提供可复用的 Skills 模块。 项目地址: https://gitcode.com/cann/cannbot-skills 执行顺序&#xff08;最高优先级&#xff…

作者头像 李华
网站建设 2026/7/4 6:56:25

CANN/asc-devkit GlobalTensor GetSize函数

GetSize 【免费下载链接】asc-devkit 本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言&#xff0c;原生支持C和C标准规范&#xff0c;主要由类库和语言扩展层构成&#xff0c;提供多层级API&#xff0c;满足多维场景算子开发诉求。 项目地址: https://gitcode.com/ca…

作者头像 李华
网站建设 2026/7/4 6:55:40

autopprof实战教程:10个技巧快速定位Go性能瓶颈

autopprof实战教程&#xff1a;10个技巧快速定位Go性能瓶颈 【免费下载链接】autopprof Pprof made easy at development time for Go 项目地址: https://gitcode.com/gh_mirrors/au/autopprof autopprof是一款专为Go开发者打造的性能分析工具&#xff0c;它极大简化了开…

作者头像 李华
网站建设 2026/7/4 6:54:07

CANN / cannbot-skills:分册5性能评估与精度对比

分册 5&#xff1a;性能评估与精度对比 【免费下载链接】cannbot-skills CANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体&#xff0c;本仓库为其提供可复用的 Skills 模块。 项目地址: https://gitcode.com/cann/cannbot-skills 对应主流程 7&#xff5e;8&…

作者头像 李华