代码重构之过长函数

简介: 介绍如何重构一些比较长的函数
在软件开发的过程中,我们为了实现某个功能,往往都要去编译无数的函数,然后把函数组合起来完成功能,但有时候,为了图方便,在一个函数中,写了很长的业务逻辑,导致一个函数的长度超过了我们的显示器,这个时候,就要学会去分析和重构它。

一、什么是过长函数

定义

一个函数包含了过多的逻辑或者过分体现了逻辑功能的实现细节,导致函数产生过长的代码块,简单来说,过长函数就是很长的函数。(听君一席话,如听一席话)

影响

  • 功能不单一:一般一个函数,最好做到功能单一,也就是一个函数只做一件事,功能单一的函数往往长度都不会太长,如果一个函数长度太长,很有可能这个函数功能不单一;
  • 可读性差:函数过长,往往都会造成可读性差,如果一个函数超过了显示屏的显示范围,一般这个函数读起来往往会令人头疼;
  • 难以维护: 在一段很长的函数中去该某段代码,维护成本非常的高;

重构过长函数的目的

增强代码的可读性,提高维护性,使函数功能单一。

重构手段

过长的函数,一般都是采用提炼函数的方法,并用函数做什么来进行重新命名;重构过长函数的难点在于,对一个函数的功能进行分析和拆分。

二、案例

2.1 案例一

2.1.1 代码功能

下面是一个打印发票的的函数,发票内容主要包括:

  • 发票头,包含买方和卖方信息。
  • 商品详细列表,包含商品的编号、名称、数目和价格
  • 发票汇总,包含所有商品的总价。
public class Invoice {
    private final String buyer;

    private final String seller;

    private final List<InvoiceLine> lines;

    public Invoice(String buyer, String seller, List<InvoiceLine> lines) {
        this.buyer = buyer;
        this.seller = seller;
        this.lines = new ArrayList<>(lines);
    }

    public String getBuyer() {
        return buyer;
    }

    public String getSeller() {
        return seller;
    }

    public List<InvoiceLine> getLines() {
        return Collections.unmodifiableList(lines);
    }

}

public class InvoiceLine {
    private final String product;
    private final float quantity;
    private final float price;

    public InvoiceLine(String product, int quantity, int price) {
        this.product = product;
        this.quantity = quantity;
        this.price = price;
    }

    public String getProduct() {
        return product;
    }

    public float getQuantity() {
        return quantity;
    }

    public float getPrice() {
        return price;
    }
}


public class InvoicePrinter {
    public static void print(Invoice invoice, PrintStream printStream) {
        printStream.println("====== INVOICE ======");
        printStream.println("Buyer: " + invoice.getBuyer());
        printStream.println("Seller: " + invoice.getSeller());

        printStream.println("------ Detail ------");
        printStream.println("ln\tProduct\tPrice\tQt\tAmount");
        float total = 0;
        for (int lineId = 0; lineId < invoice.getLines().size(); ++lineId) {
            final InvoiceLine line = invoice.getLines().get(lineId);
            String product = line.getProduct();
            float price = line.getPrice();
            float quantity = line.getQuantity();
            float amount = price * quantity;
            if (product == null || "".equals(product.trim())
                    || price <= 0 || quantity <= 0) {
                printStream.println(lineId + "\tInvalid");
            } else {
                printStream.println(lineId + "\t" + product + "\t" + price
                        + "\t" + quantity + "\t" + amount);
            }
            total += amount;
        }
        printStream.println("------ Total ------");
        printStream.println(total);
    }
}

运行效果如下:
在这里插入图片描述

2.1.2 问题分析

  1. 代码过长,所有逻辑都在一个print方法中,这个方法过分体现细节。
  2. for循环功能复杂,它既要获取数据,然后打印,同时还要计算商品总价
  3. if语句判断商品是否有效,表达式比较复杂。
  4. for循环中关于商品的临时变量太多。
  5. 类功能不够内聚,这个类的方法是print,应该专注于打印,计算总价不应该是打印机的职责。

### 功能模块分析
1、for循环打印和计算抽离
在这里插入图片描述

2、发票分为打印头,打印细节、打印价格,可以考虑根据三处不同的打印,拆分为三个函数,重构时选中对应的代码段,使用Ctrl+Shift+M,快捷重构
在这里插入图片描述
3、接下来依次重构子方法,printHeader已经很简单了,没有重构的必要,主要重构printDetail方法,这个方法比较长,很多一部分原因是因为for循环的临时变量过多,所以想办法删除临时变量,这里删除临时变量的方法是:通过查询取代临时变量和通过功能内聚取消临时变量,如图:
在这里插入图片描述
4、代码中的if判断此商品是否有效,可以把判断的语句内聚到line的方法中
在这里插入图片描述
在这里插入图片描述
5、for循环中,根据打印每行line对象的代码,也可以提炼到一个函数中专门负责每行的打印
在这里插入图片描述
6、最后重构printFooter方法,printFoot方法用于打印总价,这里使用功能内聚的方法,把计算总价的方法定义到Invoice对象中,printFooter方法直接取值就可以了。
在这里插入图片描述
在这里插入图片描述
重构后的代码如下:

public class Invoice {
    private final String buyer;

    private final String seller;

    private final List<InvoiceLine> lines;

    public Invoice(String buyer, String seller, List<InvoiceLine> lines) {
        this.buyer = buyer;
        this.seller = seller;
        this.lines = new ArrayList<>(lines);
    }

    public float calcAmount() {
        float total = 0;
        for (int lineId = 0; lineId < getLines().size(); ++lineId) {
            final InvoiceLine line = getLines().get(lineId);
            total += line.getAmount();
        }
        return total;
    }
    public String getBuyer() {
        return buyer;
    }

    public String getSeller() {
        return seller;
    }

    public List<InvoiceLine> getLines() {
        return Collections.unmodifiableList(lines);
    }

}


public class InvoiceLine {
    private final String product;
    private final float quantity;
    private final float price;

    public InvoiceLine(String product, int quantity, int price) {
        this.product = product;
        this.quantity = quantity;
        this.price = price;
    }
    boolean isValid() {
        return product == null || "".equals(product.trim())
                || price <= 0 || quantity <= 0;
    }

    public String getProduct() {
        return product;
    }

    public float getQuantity() {
        return quantity;
    }

    public float getPrice() {
        return price;
    }

    public float getAmount() {
        return price * quantity;
    }
}
public class InvoicePrinter {
    public static void print(Invoice invoice, PrintStream printStream) {
        printHeader(invoice, printStream);
        printDetail(invoice, printStream);
        printFooter(invoice, printStream);
    }

    public static void printFooter(Invoice invoice, PrintStream printStream) {
        printStream.println("------ Total ------");
        printStream.println(invoice.calcAmount());
    }

    public static void printDetail(Invoice invoice, PrintStream printStream) {
        printStream.println("------ Detail ------");
        printStream.println("ln\tProduct\tPrice\tQt\tAmount");
        for (int lineId = 0; lineId < invoice.getLines().size(); ++lineId) {
            final InvoiceLine line = invoice.getLines().get(lineId);
            printInvoiceLine(printStream, lineId, line);
        }
    }

    public static void printInvoiceLine(PrintStream printStream, int lineId, InvoiceLine line) {
        if (line.isValid()) {
            printStream.println(lineId + "\tInvalid");
        } else {
            printStream.println(lineId + "\t" + line.getProduct() + "\t" + line.getPrice()
                    + "\t" + line.getQuantity() + "\t" + line.getAmount());
        }
    }

    public static void printHeader(Invoice invoice, PrintStream printStream) {
        printStream.println("====== INVOICE ======");
        printStream.println("Buyer: " + invoice.getBuyer());
        printStream.println("Seller: " + invoice.getSeller());
    }
}

2.2 案例2

这是一个保持数据的方法,不同的数据类型,选中不同的保存方式。
在这里插入图片描述
问题:switch语句太长,而且随着数据类型增大,switch也需要不停的扩展。
解决方法:将每个switch,抽取为一个方法。
在这里插入图片描述

相关文章
|
7月前
在代码优化过程中,常见的错误和bug包括以下几点
在代码优化过程中,常见的错误和bug包括以下几点
|
7月前
|
设计模式 算法 前端开发
有什么可以减少注释,但依然能让他人看得懂代码的方法吗?
有什么可以减少注释,但依然能让他人看得懂代码的方法吗?
60 0
|
5月前
|
开发者
代码可读性问题之避免代码中的“副作用”,如何解决
代码可读性问题之避免代码中的“副作用”,如何解决
|
7月前
|
算法 程序员
为何程序员在编写程序时难以一次性将所有代码完美无瑕地完成,而是需要经历反复修改Bug的过程?
为何程序员在编写程序时难以一次性将所有代码完美无瑕地完成,而是需要经历反复修改Bug的过程?
79 7
|
测试技术
代码为啥不能过度优化
代码为啥不能过度优化
82 0
|
开发工具
代码重构之重复代码处理
介绍使用IDEA去重构重复的代码块
代码重构之重复代码处理
生成器运行时机导致的难以察觉的 bug
生成器运行时机导致的难以察觉的 bug
78 0
关于《生成器运行时机导致的难以察觉的 bug》勘误
关于《生成器运行时机导致的难以察觉的 bug》勘误
81 0
《代码重构》之方法到底多长算“长”?(下)
《代码重构》之方法到底多长算“长”?
108 0