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

  • 前言

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

    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,还需要做一些具体的取值,传值,赋值操作.

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

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

  变量尽可能少: 函数内部的变量,有效范围是整个函数,如果我们在函数前面声明了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,

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

  Ps:其实委托和lambda等函数式思维的引入,真的可以给我们带来很多新的思维启发,  不过可能是我们以前都太习惯于过程式的编码, 还需要锻炼锻炼这种新的开发理念吧.

  Ps2: 关于这种函数式写法的一个非常炫酷的示例,可以参考下csdn .NET斑竹caozhy写的一个数独游戏

时间: 2024-08-08 07:57:55

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

Findbug在项目中的运用--提高代码质量

 FindBugs是一个静态分析工具,它检查类或者 JAR文件,将字节码与一组缺陷模式进行对比以发现可能的问题.有了静态分析工具,就可以在不实际运行程序的情况对软件进行分析 第一 手动安装 在Eclipse点击菜单中Help-->菜单 第二:输入地址: http://findbugs.cs.umd.edu/eclipse,出现版本列表: 按照一步步提示安装重启即可 =================================================== 2) (Re-)star

提高代码质量:如何编写函数

阅读目录 命名 函数参数 编写函数体 总结 函数是实现程序功能的最基本单位,每一个程序都是由一个个最基本的函数构成的.写好一个函数是提高程序代码质量最关键的一步.本文就函数的编写,从函数命名,代码分布,技巧等方面入手,谈谈如何写好一个可读性高.易维护,易测试的函数. 回到顶部 命名 首先从命名说起,命名是提高可读性的第一步.如何为变量和函数命名一直是开发者心中的痛点之一,对于母语非英语的我们来说,更是难上加难.下面我来说说如何为函数命名的一些想法和感受: 采用统一的命名规则 在谈及如何为函数取一

如何提高代码质量

一.代码质量 软件是交付给用户,并由用户体验的产品:代码则是对软件正确且详细的描述,所以代码质量关系到软件产品的质量.虽然软件质量不等于代码质量,但是代码上的缺陷会严重的影响到软件产品的质量.因此,为提高代码质量的投入是值得的. 二.软件产品质量通常可以从以下六个方面去衡量 功能性,即软件是否满足了客户业务要求: 可用性,即衡量用户使用软件需要付出多大的努力: 可靠性,即软件是否能够一直处在一个稳定的状态上满足可用性: 高效性,即衡量软件正常运行需要耗费多少物理资源: 可维护性,即衡量对已经完成

如何提高代码质量(转)

原文:如何提高代码质量 1.软件产品质量 软件产品质量通常可以从以下六个方面去衡量(定义) : l         功能性(Functionality),即软件是否满足了客户业务要求: l         可用性(Usability),即衡量用户使用软件需要付出多大的努力: l         可靠性(Reliability),即软件是否能够一直处在一个稳定的状态上满足可用性: l         高效性(Efficiency),即衡量软件正常运行需要耗费多少物理资源: l         可维

进阶篇第八期:任性的提高代码质量(二)

在任性的提高代码质量里面,上期小弟提到了关于代码质量最基本的问题:代码规范 那么在这一期里,小弟会写一下关于MVC的简单使用,那么废话不多说,一会儿直接上代码 Model类: 我们先创建几个属性来弄一下吧,这里如果有某种类型多种状态,请用枚举来弄   #import <Foundation/Foundation.h> typedef enum : NSUInteger {     SWHButtonTypeNone,     SWHButtonTypeUp,     SWHButtonType

(转)提高代码质量---one

1. 摘要 这是烂代码系列的第二篇,在文章中我会跟大家讨论一下如何尽可能高效和客观的评价代码的优劣. 在发布了关于烂代码的那些事(上)之后,发现这篇文章竟然意外的很受欢迎,很多人也描(tu)述(cao)了各自代码中这样或者那样的问题. 最近部门在组织bootcamp,正好我负责培训代码质量部分,在培训课程中让大家花了不少时间去讨论.改进.完善自己的代码.虽然刚毕业的同 学对于代码质量都很用心,但最终呈现出来的质量仍然没能达到“十分优秀”的程度. 究其原因,主要是不了解好的代码“应该”是什么样的.

用 Eclipse 插件提高代码质量

如果能在构建代码前发现代码中潜在的问题会怎么样呢?很有趣的是,Eclipse 插件中就有这样的工具,比如 JDepend 和 CheckStyle,它们能帮您在软件问题暴露前发现这些问题.在 让开发自动化 的本期文章中,自动化专家 Paul Duvall 将带来一些关于 Eclipse 插件的例子,您可以安装.配置和使用这些静态分析插件,以便在开发生命周期的早期预防问题. 关于本系列 作为一名开发人员,我们的工作就是为终端用户将过程自动化:然而,我们当中有很多人却忽视了将我们自己的开发过程自动化

第01篇 提高代码质量插件

我使用插件对我的代码进行分析,我这里使用的checkStyle的插件,还有很多其他的插件,用了一个,其他都差不多,还有一个PMD的插件, 一直重来没有注重代码规范,所以这里我开始注意 第一步:安装CheckStyle插件 ? ? ? ? 出现如下窗口之后,选择ADD ? ? ? ? 之后,Namd的名称一般都是插件名称,路径为下载路径 直接update, 添加更新源地址:http://eclipse-cs.sf.net/update/, 也可以从http://sourceforge.net/pr

提高代码质量的重要手段:将细节隐藏起来

「信息隐藏」在软件开发领域中是一个非常重要的核心要点, 它的另一个名称叫做「封装」, 但是因为现代面向对象技术流行的原因, 「封装」似乎已被视为和private是等价的, 这就导致了封装的含意并不那么准确了, 事实上它的使用范围在代码的编写中无处不在, private只是封装的其中一项用途而已. 因为封装一词已经被误用太久,所以使用「信息隐藏」能更简单的阐述清楚这个概念,这能避免受对「封装」先入为主的错误理解的影响. 信息隐藏, 顾名思义就是将信息给隐藏起来. 信息是什么? 在编程语言中, 信息