前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >函数重构之道

函数重构之道

作者头像
栋先生
发布于 2018-09-29 09:00:24
发布于 2018-09-29 09:00:24
44700
代码可运行
举报
文章被收录于专栏:Java成长之路Java成长之路
运行总次数:0
代码可运行

我们先来介绍写的比较长的函数。

以下代码做了好几件事情。它创建缓冲区、获取页面、搜索继承下来的页面、渲染路径、添加神秘的字符串、生产HTML等等。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    // Listing 3-1
    public static String testableHtml(
            PageData pageData,
            boolean includeSuiteSetup
    ) throws Exception {
        WikiPage wikiPage = pageData.getWikiPage();
        StringBuffer buffer = new StringBuffer();
        if (pageData.hasAttribute("Test")) {
            if (includeSuiteSetup) {
                WikiPage suiteSetup = PageCrawlerImpl.getInheritedPage(
                        SuiteResponder.SUITE_SETUP_NAME, wikiPage
                );
                if (suiteSetup != null) {
                    WikiPagePath pagePath = suiteSetup.getPageCrawler().getFullPath(suiteSetup);
                    String pagePathName = PathParser.render(pagePath);
                    buffer.append("!include -setup .")
                            .append(pagePathName)
                            .append("\n");
                }
            }
            WikiPage setup = PageCrawlerImpl.getInheritedPage("SetUp", wikiPage);
            if (setup != null) {
                WikiPagePath setupPath = wikiPage.getPageCrawler().getFullPath(setup);
                String setupPathName = PathParser.render(setupPath);
                buffer.append("!include -setup .")
                        .append(setupPathName)
                        .append("\n");
            }
        }
        buffer.append(pageData.getContent());
        if (pageData.hasAttribute("Test")) {
            WikiPage teardown = PageCrawlerImpl.getInheritedPage("TearDown", wikiPage);
            if (teardown != null) {
                WikiPagePath tearDownPath = wikiPage.getPageCrawler().getFullPath(teardown);
                String tearDownPathName = PathParser.render(tearDownPath);
                buffer.append("!include -teardown .")
                        .append(tearDownPathName)
                        .append("\n");
            }
            if (includeSuiteSetup) {
                WikiPage suiteTeardown = PageCrawlerImpl.getInheritedPage(
                        SuiteResponder.SUITE_TEARDOWN_NAME,
                        wikiPage
                );
                if (suiteTeardown != null) {
                    WikiPagePath pagePath = suiteTeardown.getPageCrawler().getFullPath(suiteTeardown);
                    String pagePathName = PathParser.render(pagePath);
                    buffer.append("!include -teardown .")
                            .append(pagePathName)
                            .append("\n");
                }
            }
        }
        pageData.setContent(buffer.toString());
        return pageData.getHtml();
    }

上面这个函数的缺点有很多缺点:如代码太长,有太多不同层级的抽象,奇怪的字符串和函数调用,混以双重嵌套、用标识来控制if语句等。

下面我们来介绍怎么去重构上面这种函数的方法。

短小

  1. 一个函数最长不能超过十行。
  2. if、else、while语句中的代码块应该只有一行。该行大抵应该是一个函数调用语句。这样不但能保持函数短小,而且,因为块内调用函数用于较具有说明性的名称,从而增加了文档上的价值。

只做一件事

函数应该做一件事。做好这件事。只做这一件事。

什么叫做函数只做一件事情呢?如果函数只是做该函数名下同一抽象层上的步骤,则函数还是只做了一件事。

要判断函数是否不止做了一件事,还可以看是否能再拆出一个函数,该函数不仅只是单纯地重新诠释其实现。

例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    //3.3 将设置和拆解包纳到测试页面中

    public static String renderPageWithSetupsAndTeardowns(PageData pageData, boolean isSuite) throws Exception{

        //1. 判断是否为测试页面

        if (isTestPage(pageData)){

            //2. 如果是,则容纳进设置和分拆步骤

            includeSetupAndTeardownPages(pageData,isSuite);

        }

        //3. 渲染成HTML

        return pageData.getHtml();

    }

每个函数一个抽象层级

每个函数就像是文章的一个段落一样,每个段落都描述当前的抽象层级,并引用位于下一抽象层级的后续段落。

例如:

要容纳设置和分拆步骤,就先容纳设置步骤,然后纳入测试页面内容,再纳入分拆步骤。

要容纳设置步骤,如果是套件,就纳入套件设置步骤,然后再纳入普通设置步骤。

要容纳套件设置步骤,先搜索“SuiteSetUp”页面的上级继承关系,在添加一个包括该页面路径的语句。

在搜索…

switch语句

switch语句的主要问题在于重复。其次违反了单一权责原则,违反了开放闭合原则。

解决方法:利用多态,使用策略模式或状态模式来解决。

函数参数

函数参数越少越好,最多不能超过3个。

  1. 一元函数:又返回值函数,适用于转换或操作参数的场景。无返回值的函数,如事件。
  2. 标识参数:不要向函数中传入boolean值。这样做,方法签名会变得复杂起来,函数将不止做一件事情。如果表示为true会这样做,标识为false会那样做。
  3. 二元函数:有两个参数的函数会比一元函数难懂,需要读取上下文才能明白两个参数的意思和顺序。尽量将转换为一元函数,可以通过将函数写到其中一个参数类的内部,或把参数当前当前类的成员变量,从而无需传递。或者构建一个新类,通过构造器传递参数。
  4. 三元函数:尽量不要写。
  5. 参数对象:如果函数需要两个、三个或三个以上参数,就说明需要将其中的一些参数封装成类了。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Circle makeCircle(double x, double y, double radius);

Circle makeCircle(Point center, double radius);//将double x和double y重构为 Point类

无副作用

函数名和函数体所实现的功能要相对应,不要添加多余的额外功能。

例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class UserValidator {

    private Cryptographer cryptographer;



    public boolean checkPassword(String userName, String password) {

        User user = UserGateway.findByName(userName);

        if (user != User.NULL) {

            String codedPhrase = user.getPhraseEncodedByPassword();

            String phrase = cryptographer.decrypt(codedPhrase, password);

            if ("Valid Password".equals(phrase)) {

                Session.initialize();

                return true;

            }

        }

        return false;

    }

}

以上代码的副作用在于对Sessio.initialize()的调用。checkPassword函数,顾名思义,是用来检查密码的。名称并未暗示它会初始化该次会话。

输出参数

不能通过输入参数来输出返回值,容易产生歧义。

例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
appendFooter(s)//使用输出参数,容易产生迷惑

public void appendFooter(StringBuffer report);

应改为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
report.appendFooter();

分隔指令与询问

一个函数要么做什么事情,要么回答什么事情,但二者不能不能兼得。

例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean set(String attribute, String value);

此函数设置某个属性,如果成功返回true,如果不存在那个属性返回false。这样就会导致以下语句:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if(set("username","unclebob"))...

缺点:函数名表意不清楚,函数功能不单一,容易让调用者产生困扰。

解决方案:把指令与询问分隔开来,防止混淆的发生。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if(attributeExists("username"){

 setAttribute("username","unclebob");

  ...

}

使用异常替代错误码

使用错误码会导致深层次的嵌套结构。当返回错误码时,就是在要求调用者立即处理错误。

例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    if(deletePage(page) ==E_OK) {



        if (registry.deleteReference(page.name) == E_OK) {

            if (configKeys.deleteKey(page.name.makeKey()) == E_OK) {

                logger.log("page deleted");

            } else {

                logger.log("configKey not deleted");

            }

        } else {



            logger.log("deleteReference from registry failed");



        }

    } else{

        logger.log("delete failed");

        return E_ERROR;

    }

使用异常处理错误码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    try {

        deletePage(page);

        registry.deleteReference(page.name);

        configKeys.deleteKey(page.name.makeKey());

    } catch (Exception e) {

        logger.log(e.getMessage());

    }

抽离try/catch代码块

try/catch代码块容易搞乱代码结构。

函数应该只做一件事。错误处理就是一件事。如果关键字try在某个函数中存在,它就应该是这个函数的第一个单词,并且在catch/finally代码块后面也不该有其他内容。

所以把try和catch代码块的主体部分抽离出来,形成一个单独的函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public void delete(Page page) {

        try {

            deletePageAndAllReferences(page); }

        catch (Exception e) {

            logError(e);

        }

    }

    private void deletePageAndAllReferences(Page page) throws Exception {

        deletePage(page);

        registry.deleteReference(page.name);

        configKeys.deleteKey(page.name.makeKey());

    }

    private void logError(Exception e) {

        logger.log(e.getMessage());

    }

以上代码将错误处理和真正的删除page函数分离,可以使得代码更加容易理解和修改。

如何写出简洁函数

先想什么写什么,然后对代码进行推敲打磨,分解函数、修改名称、消除重复。

以下代码是对本文最开始的过长函数testableHtml的重构。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class SetupTeardownIncluder {
    private PageData pageData;
    private boolean isSuite;
    private WikiPage testPage;
    private StringBuffer newPageContent;
    private PageCrawler pageCrawler;

    public static String render(PageData pageData) throws Exception {
        return render(pageData, false);
    }

    private static String render(PageData pageData, boolean isSuite) throws Exception {
        return new SetupTeardownIncluder(pageData).render(isSuite);
    }

    private SetupTeardownIncluder(PageData pageData) {
        this.pageData = pageData;
        testPage = pageData.getWikiPage();
        pageCrawler = testPage.getPageCrawler();
        newPageContent = new StringBuffer();
    }

    private String render(boolean isSuite) throws Exception {
        this.isSuite = isSuite;
        if (isTestPage()) {
            includeSetupAndTeardownPages();
        }
        return pageData.getHtml();
    }

    private boolean isTestPage() throws Exception {
        return pageData.hasAttribute("Test");
    }

    private void includeSetupAndTeardownPages() throws Exception {
        includeSetupPages();
        includePageContent();
        includeTeardownPages();
        updatePageContent();
    }

    private void includeSetupPages() throws Exception {
        if (isSuite) {
            includeSuiteSetupPage();
        }
        includeSetupPage();
    }

    private void includeSuiteSetupPage() throws Exception {
        include(SuiteResponder.SUITE_SETUP_NAME, "-setup");
    }


    private void includeSetupPage() {
        include("SetUp", "-setup");
    }

    private void includePageContent() {
        newPageContent.append(pageData.getContent());
    }

    private void includeTeardownPages() {
        includeTeardownPage();
        if (isSuite) {
            includeSuiteTeardownPage();
        }
    }

    private void includeTeardownPage() {
        include("TearDown", "-teardown");
    }

    private void includeSuiteTeardownPage() {
        include(SuiteResponder.SUITE_TEARDOWN_NAME, "-teardown");
    }

    private void updatePageContent() {
        pageData.setContent(newPageContent.toString());
    }

    private void include(String pageName, String arg) {
        WikiPage inheritedPage = findInheritedPage(pageName);
        if (inheritedPage != null) {
            String pagePathName = getPathNameForPage(inheritedPage);
            buildIncludeDirective(pagePathName, arg);
        }
    }

    private WikiPage findInheritedPage(String pageName) {
        return PageCrawlerImpl.getInheritedPage(pageName, testPage);
    }

    private String getPathNameForPage(WikiPage page) {
        WikiPagePath pagePath = pageCrawler.getFullPath(page);
        return PathParser.render(pagePath);
    }

    private void buildIncludeDirective(String pagePathName, String arg) {
        newPageContent.
                append("\n!include").
                append(arg).
                append(" .").
                append(pagePathName).
                append("\n");
    }
}

本文源代码地址:github

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018年07月29日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
深入理解简单设计
通过所有测试原则意味着我们开发的功能满足客户的需求,这是简单设计的底线原则。该原则同时隐含地告知与客户或领域专家(需求分析师)充分沟通的重要性。
张逸
2023/03/23
3010
深入理解简单设计
代码整洁之道
1.命名规范 1.1.模糊的命名 //代码的模糊度 public List<int[]> getThem(){ List<int[]> list1 = new ArrayList<int[]>(); for (int[] x : theList){ if (x[0] == 4){ list1.add(x); } } return list1; } 疑问: (1) theList中是什么类型? (2) theList
用户9831583
2022/06/16
2450
《代码整洁之道》读书笔记
我在短暂的工作经历中(4 个月),犯下过不少错,少部分是因为经验,但大部分的情况下都是因为对代码没有足够的敬畏之心导致的,并且在工作中也遇到过一些很有意思的代码,所以今天就着这本《代码整洁之道》,来谈一谈对于代码的感受和一些想法。(Ps:想吐槽一下这本书挺魔怔的..)
Java3y
2019/09/18
3860
Java 代码精简之道
其中:“道”指“规律、道理、理论”,“术”指“方法、技巧、技术”。意思是:“道”是“术”的灵魂,“术”是“道”的肉体;可以用“道”来统管“术”,也可以从“术”中获得“道”。
JAVA葵花宝典
2020/06/04
2.1K0
代码整洁之道-读书笔记之函数
一函数理论上只做一件事情,只做一个抽象层次的事情,通俗的说就是看看当前函数是否还可以拆分出一个函数,如果可以说明就不是做一件事
特特
2022/09/30
4670
高质量代码的特征
回想起来,我觉得我们似乎在误读Uncle Bob的Clean Code,至少我们错误地将所谓Clean与可读性代码简单地划上了等号。尤为不幸的是,在Clean Code一书中,从第二章到第五章都围绕着可读性代码做文章,于是加深了这种错误的印象。 许多具有代码洁癖的程序员将代码可读性视为神圣不可侵犯的真理,并奉其为高质量代码的最重要特征,封上了“神坛”。殊不知,Uncle Bob在Clean Code的第一章就通过别人之口对所谓“Clean Code”进行了正名:所谓整洁代码并非仅仅是“清晰”这么简单。 按照
张逸
2018/03/07
1.2K0
工作多年后我更了解了UT的重要性
对于有经验的开发写单元测试是非常有必要的,并且对自己的代码质量以及编码能力也是有提高的。单元测试可以帮助减少bug泄露,通过运行单元测试可以直接测试各个功能的正确性,bug可以提前发现并解决,由于可以跟断点,所以能够比较快的定位问题,比泄露到生产环境再定位要代价小很多。同时充足的UT是保证重构正确性的有效手段,有了足够的UT防护,才能放开手脚大胆重构已有代码,工 作多年后更了解了UT,了解了UT的重要性。
你呀不牛
2021/05/28
1.6K0
Clean Code系列之异常处理
先前已经对异常如何设计,如何实践异常都写了几篇阐述了。再一次从Clean Code角度来谈谈异常的使用。
码农戏码
2022/11/18
3920
这样写代码,同事乐开花
鱼皮最新原创项目教程,欢迎学习 大家好,我是鱼皮,记得我在大学的时候,看过一本书叫《代码整洁之道》,让我受益匪浅。 工作多年后,越发觉得代码整洁真的是太重要了!尤其是在团队开发中,写出优雅工整的代码,能让同事更乐于跟你合作。 今天分享的这篇文章,希望用最快的速度来帮助大家了解到整洁的代码对项目、公司和个人的重要性,并且学会如何书写整洁的代码. 下面,将通过命名、类、函数、测试这四个章节,使我们的代码变得整洁。 原文链接:https://www.cnblogs.com/liuboren/p/17017421
程序员鱼皮
2023/03/29
2530
这样写代码,同事乐开花
代码整洁之道-读书笔记之格式
先明确一下,代码格式很重要。代码格式不可忽略,必须严肃对待。代码格式关乎沟通,而沟通是专业开发者的头等大事。
特特
2022/10/27
4190
万能实体类(PageDate)
哈喽。。。。我回来了。好久没写文章了,这段时间太忙了。上家公司倒闭了,这段时间找工作,熟悉新的工作,到现在入职了2周,现在终于有时间给大家分享技术了。 ---- 他们说我你分享的文章会被人讽刺的,写的太烂了,我不怕,只要我还有梦,我就一直写下去,人不死终会出头。 今天给大家分享一个万能的实体类。 相当于是一个map根据传什么值都可以 package com.ylxy.util; import java.io.BufferedReader; import java.io.Reader; imp
猿码优创
2019/07/28
5580
设计模式整理 顶
Iterator模式可以帮助我们分离具体的集合跟遍历,就是在代码中更换了集合,也可以不需要重新调用新集合的方法。
算法之名
2019/08/20
8990
设计模式整理
                                                                            顶
log.error()底层到底做了些啥?
今天给大家介绍一下logback日志,底层是如何实现的。这边我们打印一下error级别的日志,看看从log.error到输出磁盘,这个过程中到底发生了些什么,并从源码级别揭秘整个日常的输出过程。
林老师带你学编程
2020/03/18
1K0
log.error()底层到底做了些啥?
JavaEE之MVC框架组合(SpringMVC+Spring4.0+Mybatis3.2)搭建过程详解
很久之前就想写的教程,因为忙着找实习的原因,没有整理出来,首先SSM框架组合(SpringMVC+Spring+Mybatis)和SSH(Spring4.0+Struts2+Hibernate4.0)组合是外面企业里很常用的两种MVC架构,本博客介绍SSM框架组合,这种MVC架构的搭建过程
SmileNicky
2019/01/17
7870
Java实现对文本文件MD5加密并ftp传送到远程主机目录
如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。
java架构师
2018/12/27
8570
JavaWeb之分页的实现——基于Mysql(通用)
相信大家也在网站上看到的分页效果的吧!那么现在来一起看看他的思路以及代码还有效果图吧
用户10196776
2022/11/22
1.5K0
JavaWeb之分页的实现——基于Mysql(通用)
SpringBoot2.0 整合 SpringSecurity 框架,实现用户权限管理
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring的IOC,DI,AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为安全控制编写大量重复代码的工作。
知了一笑
2019/07/19
8100
Flutter Boost 混合开发实践与源码解析( Android )
1. 简介 Flutter Boost 是闲鱼团队开发的一个 Flutter 混合开发框架,项目背景可以看看闲鱼的这篇文章:码上用它开始Flutter混合开发——FlutterBoost。 文章中主要讲述了多引擎存在一些实际问题,所以闲鱼目前采用的混合方案是共享同一个引擎的方案。而 Flutter Boost 的 Feature 如下: 可复用通用型混合方案 支持更加复杂的混合模式,比如支持主页Tab这种情况 无侵入性方案:不再依赖修改Flutter的方案 支持通用页面生命周期 统一明确的设计概念 Flu
QQ音乐前端团队
2020/04/07
2.4K0
基于Fusioncharts的报表统计
本博客介绍fusioncharts插件的使用 先了解fusioncharts插件,fusioncharts是一款基于XML和flash的报表组件,支持Java、PHP、AngularJS等等开发语言,所以,开发出来,加入swf文件,就可以出现动态效果的报表统计,具有2D和3D效果的图表,下面是官网和详细分类
SmileNicky
2019/01/17
1.1K0
SSM框架使用POI技术导出Excel表
POI框架是Apache开源的可以导出导入Excel表的,本博客介绍在SSM(Spring+SpringMVC+Mybatis)项目里,如何使用POI框架,导出Excel表
SmileNicky
2019/01/17
7520
相关推荐
深入理解简单设计
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验