• 代码细节重构:请对我的代码指手划脚(四)


    代码细节重构:请对我的代码指手划脚(四)

     

     

    这是上周在代码审阅会议上讨论到的一段代码,这段代码的作用是根据指定记录数量和页面大小来计算最大分页数量的。

    目标代码

    复制代码
     1 /// <summary>
     2 /// 根据指定记录数量和页面大小返回分页页数。
     3 /// </summary>
     4 /// <param name="totalRecords">记录总数。</param>
     5 /// <param name="pageSize">页面大小。</param>
     6 /// <returns>返回分页页数。</returns>
     7 public static long ComputePages(int totalRecords, int pageSize)
     8 {
     9     var total = Convert.ToDouble(totalRecords);
    10     var size = Convert.ToDouble(pageSize);
    11 
    12     var temp = total / size;
    13 
    14     return (long)Math.Ceiling(temp);
    15 }
    复制代码

    代码问题

    在代码审阅会议上起初并没有留意到这段代码,但随着翻阅了多个代码页之后便发现“ComputePages”这个方法被多次重新定义,且功能完全一样。应该是开发人员复制粘贴过来的,原本也是想针对复制粘贴来讲讲这段代码的,但后来发现了更多的问题:

    1. 代码重用问题。通过在整个解决方案搜索,发现这个方法被原封不动的复制粘贴了36次,总计在37个类当中出现了37次,而且这些类全部都是静态类。这里的问题就是对于重复出现的代码逻辑,而且是业务逻辑简单且又完全一样的代码,没能实现代码重用,这会给后续的代码维护带来诸多不便;
    2. 类型安全问题。这段代码的操作对象都是数字,数字属于值类型,类型安全问题不会显得特别突出。但数字本身却会有一些其他的隐含问题,比如数据类型转换、分母不能为零等;
    3. 分母不能为零。很显然,这段代码里面包含了除法,而对于除法运算没有做分母不能为零的逻辑处理。对于新手程序员来说这是很容易被忽略的问题,但对于有一定经验的研发人员来讲,这一点势必已经刻骨铭心了;
    4. 代码性能问题。我们知道在托管代码中进行各种类型转换都会带来不小的性能损失,而如上代码便存在着一些可优化的部分;
    5. 业务逻辑问题。认真观察和分析如上代码,你会发现该方法的输入参数在特例条件下会输出不符合实际期望的结果。比如输出结果可能是小于0的负数;

    重构建议

    找到了问题所在,那么就会有一定的解决方案:

    • 对于代码重用,可以在某个业务逻辑相关的类中集中定义这个方法,而不是分散到不同的类中。不过,本文不会具体讨论这个问题;
    • 对于类型安全、类型转换,具体到数字的时候,我们应该适当的考虑一下通过数学的方法来解决这些问题,而不是死板的套取程序语言规范;
    • 对于分母不能为零这一类定理性的问题,我们在编码过程中一定要小心在意!千万不能抱着侥幸心理;
    • 对于业务逻辑上的问题,我们在编码之前一定要认真思考、琢磨,弄清楚要解决的问题及可能产生的后果;

    重构结果

    为了使得大家更容易理解,我将代码重构的一些具体思路和想法以备注的方式贴在代码里面了,大家认真观察,有问题可以提出。

    复制代码
     1 /// <summary>
     2 /// 根据指定记录数量和页面大小返回分页页数。
     3 /// </summary>
     4 /// <param name="totalRecords">记录总数。</param>
     5 /// <param name="pageSize">页面大小。</param>
     6 /// <returns>返回分页页数。</returns>
     7 /* 
     8     * 1、方法返回值从long修改为int。原因如下:
     9     *    ·输入参数皆为int,且运算关系为除法,所以输出结果必定在int范围之内。不使用unit是因为那会给整个解决方案的其他部分代码带来很多的编码痛苦;
    10     *    ·小小的内存性能改进;
    11     */
    12 public static int ComputePages(int totalRecords, int pageSize)
    13 {
    14     /*
    15         * 2、对记录总数的类型安全判断和业务逻辑处理:
    16         *    ·记录总数肯定是大于等于零的数;
    17         *    ·记录总数为零的时候,页数肯定也是零,此时后续的代码逻辑没有意义,可以直接返回0;
    18         */
    19     if (totalRecords < 1) return 0;
    20     /*
    21         * 3、对页面大小的类型安全判断和业务逻辑处理:
    22         *    ·页面大小不能为0,因为分母不能为0;
    23         *    ·页面大小不能为负数,因为这与事实不符;
    24         *    ·当页面大小为1的时候,页数其实就是记录总数,直接返回totalRecords,这同时也避免了后续的代码无意义的执行,减少了性能损耗;
    25         */
    26     if (pageSize < 1) return totalRecords;
    27 
    28     /*
    29         * 4、对原方法业务逻辑的重构、类型转换方式的优化及性能优化:
    30         *    ·通过任何数字乘以(或除以)浮点数其结果仍然为浮点数的方式完成自动的、隐式的类型转换,这不但使得代码更加简洁,且优化了性能;
    31         *    ·代码简单、可读性强,更加容易理解业务逻辑;
    32         */
    33     return (int)Math.Ceiling(totalRecords*1.0/pageSize);
    34 }
    复制代码

    结语

    俗话说细节决定成败。作为程序员,从代码的细节我们就能看得出一个人编程水平的高低和开发经验的多寡,这也从一定意义上决定了你的成与败!

    之前我发表过很多文章,都是从人文角度来引导新手的,今天这篇文章,也正好说明了人文因素对程序员素质的影响!

    要做事,请首先做好人,对自己的代码负责就是对自己负责,对自己负责就是对团队负责,对团队负责就是对企业利益负责,因为我们是同一个团队,拥有同样的利益!

    (C++)++
    摘要: 这是上周在代码审阅会议上讨论到的一段代码,这段代码的作用是根据指定记录数量和页面大小来计算最大分页数量的。阅读全文
    posted @ 2014-01-06 13:55 陈彦铭 阅读(796) | 评论 (3) 编辑
     
    摘要: 最近老陈要针对企业邮箱做一些开发,以对接企业OA神马的,但企业邮箱唯独没有开放账号密码验证功能,很恼火!不得已,翻出早些年的Asp代码改编成了C#类,实现了一个C#下的通过POP3协议进行邮箱账号验证的类,而且还能完美支持SSL加密,貌似很实用的样子,分享给大家先!阅读全文
    posted @ 2013-10-23 17:02 陈彦铭 阅读(508) | 评论 (0) 编辑
     
    摘要: 这段代码是来自于一个真实的exe的反编译结果。我发布到社区里面之后,目前为止没有人能够给出我期望的看法或建议。阅读全文
    posted @ 2012-05-10 14:53 陈彦铭 阅读(2126) | 评论 (8) 编辑
     
    摘要: “请对我的代码指手划脚”是我们群内搞的一个不定期的常规性活动,以代码审阅和细节重构为主线,大家可以自由发表自己的意见和建议,也算得上是一种思维风暴。感觉到这个活动很有意义,有必要总结并记录下来。阅读全文
    posted @ 2012-04-29 00:08 陈彦铭 阅读(2177) | 评论 (5) 编辑
     
    摘要: “请对我的代码指手划脚”是我们群内搞的一个不定期的常规性活动,以代码审阅和细节重构为主线,大家可以自由发表自己的意见和建议,也算得上是一种思维风暴。感觉到这个活动很有意义,有必要总结并记录下来。今天我发起了4短代码,都有一定的代表性。今天我就其中的一个代码片段的重构做一个简单的总结和分享。阅读全文
    posted @ 2012-04-24 23:19 陈彦铭 阅读(2216) | 评论 (13) 编辑
     
    摘要: 本课我们主要讨论“命令解释器”的实现。命令就是指令,指令也是构成更加复杂的模板引擎的基本元素之一。至此我们可以归纳出来,模板引擎在工作的过程中,首先将字符流转换为Token流,然后再将Token流转换为Element集合(也算是流),然后将特定的Element单独拿出来或组合在一起形成指令、语句等。写一个模板引擎,和写一个小型的编译器几乎相当,因此我们需要耐心、细心!阅读全文
    posted @ 2012-04-15 10:21 陈彦铭 阅读(2455) | 评论 (3) 编辑
     
    摘要: 与置换型模板引擎不同的是,解释型模板引擎包含了一个专用解释器,有了解释器的存在就可以支持一些更加复杂而严谨的语法。熟悉设计模式的朋友此时此刻应该能够回想起来“解释器模式”,它是将一些业务封装成一个或多个命令,然后通过一个复杂的解释器(Interpreter)来解析执行来满足生产需求的。实际上,解释型模版引擎就是一个较为复杂的解释器模式的实现而已。具体到模板引擎技术上来,解释器的工作任务实际上就小了很多。按照数据封装模式的不同,解释型模板引擎分为命令解释器和反射解释器两个大类。阅读全文
    posted @ 2012-04-09 09:01 陈彦铭 阅读(2863) | 评论 (6) 编辑
     
    摘要: 在进入解释型模版引擎的探讨之前,我决定先分享一下这篇博客。因为在解释型引擎里将会引入反射的概念来实现更多、更复杂的功能。可能大家谈到反射面部肌肉都开始抽搐了吧!因为在托管语言里面,最臭名昭著的就是反射!它的性能实在是太低了,甚至在很多时候让我们无法忍受。不过不用那么纠结了,老陈今天就来分享一下如何来优化反射!阅读全文
    posted @ 2012-04-07 10:05 陈彦铭 阅读(3252) | 评论 (11) 编辑
     
    摘要: 置换型模板引擎系列是我们进入模板引擎开发领域的基础课程,这里讲述的一些原理、概念和实践方案都是后续模板引擎开发中所需要用到的,正所谓是由浅入深、循序渐进!在编写这些博文的时候,我遇到了很多阻力。为了能够让菜鸟朋友入门又不让高手们嗤之以鼻感觉到木有干货,这让老陈真的是煞费苦心!如果仅仅是开源一份代码出去,那么完成这样的项目本身可能不需要多少时间,然而要把这些组织成文字分享给大家,实在是很头疼的一件事情。阅读全文
    posted @ 2012-04-06 09:21 陈彦铭 阅读(2200) | 评论 (3) 编辑
     
    摘要: 在上一篇我们以简单明快的方式介绍了置换型模版引擎的关键技术——模板标记的流式解析。采用流式解析可以达到相当好的解析性能,因为它基本上只需要对字符串(模板)扫描一次就可以完成所有代码的解析。不像String.Split()和正则表达式那样会造成很多迭代效应。今天我们引入一个较为复杂的示例,然后封装一个实用级别的模板引擎。封装就意味着使用者无需了解内部如何实现,只需要知道如何引用即可。阅读全文
    posted @ 2012-04-05 09:41 陈彦铭 阅读(2524) | 评论 (9) 编辑
     
    摘要: 上次我们简单的认识了一下置换型模板引擎的几种情况,当然我总结的可能不够完善,希望大家继续补充。谈到按流替代式模板引擎的原理但并没有给出真正的实现。跟帖的评论中有一位朋友(Treenew Lyn)说的很好:“Token 解析其实是按一个字符一个字符去解析的”。的确是这样,而且唯有这样才能够实现更加高效、更加准确的模板引擎机制。我们首先将模板代码分解成一个一个的Token,然后按照顺序形成Token流(顺序集合),在输出的时候替换规定好的语法标记即可。阅读全文
    posted @ 2012-04-01 09:20 陈彦铭 阅读(2612) | 评论 (8) 编辑
     
    摘要: 很显然,置换型模板引擎说的就是替换式模板引擎。它的工作原理是查找和替换字符串,但这个字符串的替换过程又分为直接查找替换和按流替代输出两种。直接查找替换实现简单,但存在一定的性能障碍,而按流替代的方式性能更好一点,但理解起来却没那么容易。今天我们就专门来讨论讨论置换型模板引擎的关键技术和实现。阅读全文
    posted @ 2012-03-31 09:08 陈彦铭 阅读(3022) | 评论 (12) 编辑
     
    摘要: 最初的模板引擎是为了实现业务逻辑和代码分离,后来发现这样的模式给界面维护带来了极大的便利,这也是一种MVC模式的实现。现在我们在很多地方都可以看到模板引擎的身影,比如各种CMS、论坛、代码生成器,甚至是Mootools、JQuery等JavaScript库都用到了模板引擎。那么模板引擎到底是个什么东东呢?模板引擎在实际生产环境中有着什么样的意义?我们到底是否应该使用模板引擎技术?接下来我们会一一解答。阅读全文
    posted @ 2012-03-29 09:13 陈彦铭 阅读(4626) | 评论 (18) 编辑
     
    摘要: Levenshtein算法,用于计算两个字符串之间的Levenshtein距离。而Levenshtein距离又称为编辑距离,是指两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。 今天老陈翻译的这篇文章讲的是一种快速、内存高效的Levenshtein算法实现。阅读全文
    posted @ 2012-03-27 09:00 陈彦铭 阅读(3565) | 评论 (8) 编辑
     
    摘要: 这篇文章阐述了当使用MemoryStream处理大型数据集时经常触发的模棱两可的OutofMemoryException异常,并且介绍了一个类——MemoryTributary,他可以用来替代.NET内置的MemoryStream,并且能够支持大型数据的处理。阅读全文
    posted @ 2012-03-25 10:12 陈彦铭 阅读(3228) | 评论 (9) 编辑
     
    摘要: 很多时候我们需要以编程的方式获取命令行输出的内容,研究了不少时间,终于搞定了。获取命令行输出内容的方式有传统和异步两种方式。阅读全文
    posted @ 2012-03-23 23:53 陈彦铭 阅读(2411) | 评论 (3) 编辑
     
    摘要: 在.NET枚举类型优化探讨(二)中我们探讨了“使用类或结构来替代部分枚举类型”的方案并试图进行进一步的重构和优化,但是发现有很多限制,不但没有完成重构,且发现了很多该方案不适用的地方和缺陷。在某些情况下,这种方案会对生产带来相反的作用,所以在文中我建议不要滥用。今天我们来探讨一下使用.NET中的Attitude特性来扩展.NET枚举值的方案。阅读全文
    posted @ 2012-03-22 12:05 陈彦铭 阅读(1634) | 评论 (1) 编辑
     
    摘要: 昨天在.NET中的枚举值(一)中我提到,如果将该文中的实现进一步架构,提炼出一个抽象类作为自定义枚举类型的基类的话,肯定会对后续开发有很好的帮助。但实际上,老陈犯下了一个严重的错误……阅读全文
    posted @ 2012-03-21 11:48 陈彦铭 阅读(563) | 评论 (0) 编辑
     
    摘要: 昨天晚上通过博文《Java中的枚举值》和大家分享探讨了Java枚举值语法的非常规性和它给力的地方,该文引起了.NET猴子的一些非议,因为Java能做到的,.NET基本上也能做到。那么今天老陈就来和大家共同研究一下.NET中的枚举类型,看看它和Java相比有没有神马优势。阅读全文
    posted @ 2012-03-20 11:02 陈彦铭 阅读(1395) | 评论 (2) 编辑

    使用流程引擎整体解决方案

     

      前面对流程引擎个方面已经有了比较详细的介绍,本篇将最后介绍流程调用的整体解决方案。

      在具体项目调用流程引擎之前,我们需要做的是先把流程引擎以及可视化的流程设计器嵌入到自己的项目中,具体的嵌入方式请参考:流程引擎及流程设计器的嵌入方式

      欢迎加入流程交QQ流群:251834323。

      一、根据具体项目扩展相关接口

      在进行流程调用之前,首先需要根据具体的项目扩展流程引擎公开的相关接口(IPersonList)。此接口的作用为:把具体项目相关的人员信息、部门信息、角色信息等提供给流程引擎调用。

      IPersonList接口包含两个方法。GetPersonList及GetGlobalKeyValue,前者扩展的目的为:让流程引擎可以通过调用此方法,获得人员类型ID对应的所有的人员列表。后者扩展的目的为:使具体的人员类型对流程引擎透明化(角色,具体有那些角色、部门,具体有哪些部门等),以便可视化流程设计中对相关人员类型处理。

     IPersonList接口实现实例

      实现相关接口后即可根据具体的业务逻辑利用可视化流程设计器进行流程的设计。

      二、方法调用,推动公文流转

      由于此流程引擎并未嵌入表单设计相关事项(后期可能加入表单设计引擎),所以如果想使用此流程引擎,还需要自行设计相关流转表单。当设计好表单后,可调用流程引擎相关方法达到公文流转。

      以下提到的方法具体说明请参考:通用流程相关方法说明及调用事例

      1.调用WorkflowOperation中的方法IsExistTask或GetCurrentStep判断表单针对某流程的任务是否已经发送。

      2.如果任务未发送,调用WorkflowOperation中的SendWorkflow方法,发起公文的流转。

      3.如果任务已发送,调用TaskListOperation中的GetPersonTask或GetByFormTaskList方法获取待处理的任务列表。

      4.调用WorkflowOperation中的IsHereafter方法,判断待处理任务的节点步骤的处理人类型是否为将来指定,如果为将来指定,需弹出人员选择框选择具体的处理人,然后进入步骤5。

      5.调用TaskListOperation中的TaskListUpdate方法,对待处理任务进行处理。

      6.再次调用WorkflowOperation中的SendWorkflow方法,推动公文的流转。

      7.判断6中的方法的返回值,确定推动状态,如果未到达结束节点步骤,重复步骤3-6,否则对表单做后续的相关处理,如改变表单状态等。

      三、特殊情况的处理

      在公文的正常流转中,难免会出现许许多多的特殊情况,例如,审批人的更换、添加、移除等。在此流程引擎中,也有相关的解决方案。即,通过如下方法对处理人进行增删。

      TaskListOperation类中的AddHandlePerson方法对处理人的增加。

      TaskListOperation类中的RemoveHandlePerson方法对处理人的移除。

    以上既是,包含流转发起、流转推动、任务列表获取等在内的基本流程应用。

    注:流程引擎正在不断的努力完善中,同时希望有相同爱好的人能加入QQ群251834323,共同为开源软件做一份微薄之力。

    相关文章连接:

      通用流程设计http://www.cnblogs.com/qidq/p/workflow.html

      可视化流程设计——流程设计器演示(基于Silverlight)http://www.cnblogs.com/qidq/p/Workflow_Silverlight.html

      流程引擎及流程设计器的嵌入方式http://www.cnblogs.com/qidq/p/3499769.html

      通用流程相关方法说明及调用事例http://www.cnblogs.com/qidq/p/3504061.html

     
     
    分类: C#工作流
  • 相关阅读:
    Python记录-python执行shell命令
    Linux记录-centos升级python3
    Linux记录-mysql参数优化
    Linux记录-史上最全的MySQL高性能优化实战总结(转载)
    linux记录-docker配置mysql
    Linux记录-SVN+Jenkins+jdk+maven自动化集成部署
    Linux记录-Nginx+Tomcat负载均衡配置
    linux记录-安装elk记录(参考博文)
    linux记录-安装zabbix监控系统
    Zookeeper简单介绍
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3507990.html
Copyright © 2020-2023  润新知