nereuschen / blog

blog
44 stars 11 forks source link

整洁代码--方法 #35

Open nereuschen opened 9 years ago

nereuschen commented 9 years ago

方法

大致看明白这个方法的业务逻辑,需要话多长时间 2分钟?1分钟?20秒?

Dirty

    public void renderWebPage() {
        StringBuilder content = getContentBuilder(); content.append("<html>");
        content.append("<head>");
        for (HeaderElement headerElement : getHeaderElements()) {
            String headerEntry = headerElement.getStartTag() +      
            headerElement.getContent() + headerElement.getEndTag();
            content.append(headerEntry); }
        content.append("</head>");
        content.append("<body>");
        for (BodyElement bodyElement : getBodyElements()) {
            String bodyEntry = content.append(bodyElement); 
        }
        content.append("</body>");
        content.append("</html>");
        OutputStream output = new OutputStream(response); 
        output.write(content.toString().getBytes()); 
        output.close();
    }

简单重构之后,看懂这个方法的业务逻辑需要多长时间? 10秒钟?5秒钟?

Clean

public void renderWebPage() { 
    buildStartPage(); 
    buildHeaderContent(); 
    buildBodyContent(); 
    buildEndPage(); 
    flushPageToResponse();
}

private void buildStartPage() { /* ... */ }

private void includeHeaderContent() { /* ... */ } 

private void includeBodyContent() { /* ... */ } 

private void buildEndPage() { /* ... */ }

private void flushPageToResponse() { /* ... */ }

重构后的代码肯定比重构前的代码更容易阅读和理解

如何做到的呢?

掌握以下内容,你也可以做到

方法应该保持短小(Small)

短小的好处如下

多小才算小呢?看看大牛们怎么说的

听起来好像不可能,是不是还有点疯狂!!

只做一件事情(Do One Thing)

方法只应该做一件事情;说起来简单,实际做起来不容易

首先要能够判断出一个方法是否只做一件事情

通常的判断标准是看这个方法中的每一步是否都是同一抽象级别的方法,如果是我们可以判断该方法只做了一件事情,只是包含多个步骤而已

比如,重构后的renderWebPage方法,含5个步骤(startPage,includeHeaderContent...),每个步骤执行自己的逻辑,并且是在同一个抽象级别,所以该方法只做了一件事情

在同一个抽象级别上(One Level of Abstraction)

注意是同一抽象级别;如果一个方法里面包含了高抽象级别或者低抽象级别的

比如,这段代码

public void renderWebPage() { 

    startPage(); 

    includeHeaderContent(); 

    includeBodyContent(); 

    content.append("</html>");

    flushPageToResponse();
}

可以很明显的看出

content.append("</html>");

和其他方法不在同一个抽象级别上,这段代码是低抽象级别的代码

方法名(Function Names)

使用描述性的名字,更容易表达出业务含义 不要太在意起个描述性的名字会导致方法名过长,即使是描述性过长的名称也好过长注释的方法

article.calculate(); 
article.calculatePrice();
article.calculatePriceWithDiscount(); 
article.calculatePriceNetOfTax();

参数(Arguments and Parameters)

最小化参数个数

多少个参数比较合适

当一个方法的参数超过3个的时候,就需要将一些参数封装为对象,来替代这次参数

Dirty

public List<Article> searchArticles(String articleName,String articleNumber, 
               String articleCategory, int pageSize, int pageOffset, int order) {
    /* ... */
}

Clean

public List<Article> searchArticles(ArticleSearchCriteria criteria) { 
    /* ... */
}

critera = new ArticleSearchCriteria() .withArticleName("cleancode") 
.withArticleNumber("abc123") .withArticleCategory("softwareengineering") 
.withPageSize(10);

避免使用标示符作为参数

因为违反了单一职责原则,一个方法只该作一件事情

Dirty

public void rotate(double degrees, boolean left)

Clean

public void rotateLeft(double degrees) 
public void rotateRight(double degrees) 

封装表达式

if(!el.offsetWidth || !el.offsetHeight) {
}
var isVisible = el.offsetWidth && el.offsetHeight;
if(!isVisible) {
}

Switch 语句(Switch Statements)

If 语句()

阶梯式if else if

Dirty


    public static Map<String, Object> buildFaxContentMap(Command faxCommand) {

        Map<String, Object> faxContentMap = Maps.newHashMap();

        if (Command.BOOKING_NOTIFY == faxCommand || Command.MANUAL_NOTIFY == faxCommand) {
            faxContentMap.put("titleName", "预订通知单");
            faxContentMap.put("faxType", "预订");
        } else if (Command.CANCEL_NOTIFY == faxCommand) {
            faxContentMap.put("titleName", "取消通知单");
            faxContentMap.put("faxType", "取消");
        } else if (Command.CHANGE_NOTIFY == faxCommand) {
            faxContentMap.put("titleName", "变更通知单");
            faxContentMap.put("faxType", "变更");
        } else if (Command.OCNC_NOTIFY == faxCommand) {
            faxContentMap.put("titleName", "现付转预付通知单");
            faxContentMap.put("faxType", "");
        } else if (Command.REJECTED_NOTIFY == faxCommand) {
            faxContentMap.put("titleName", "拒单通知单");
            faxContentMap.put("faxType", "拒单");
        } else {
            faxContentMap.put("titleName", "订单详情");
            faxContentMap.put("faxType", "");
        }

        return faxContentMap;
    }

Clean


    private static Map<Command, FaxTitle> faxTitleMap = Maps.newHashMap();

    static {
        faxTitleMap.put(Command.BOOKING_NOTIFY, new FaxTitle("预订通知单", "预订"));
        faxTitleMap.put(Command.MANUAL_NOTIFY, new FaxTitle("预订通知单", "预订"));
        faxTitleMap.put(Command.CANCEL_NOTIFY, new FaxTitle("取消通知单", "取消"));
        faxTitleMap.put(Command.CHANGE_NOTIFY, new FaxTitle("变更通知单", "变更"));
        faxTitleMap.put(Command.OCNC_NOTIFY, new FaxTitle("现付转预付通知单", ""));
        faxTitleMap.put(Command.REJECTED_NOTIFY, new FaxTitle("拒单通知单", "拒单"));
    }

    public static Map<String, Object> buildFaxContentMap(Command faxCommand) {

        Map<String, Object> faxContentMap = Maps.newHashMap();

        FaxTitle faxTitle = faxTitleMap.get(faxCommand);

        if(faxTitle==null){
            faxTitle = new FaxTitle("订单详情","");
        }

        faxContentMap.put("titleName", faxTitle.getTitleName());
        faxContentMap.put("faxType", faxTitle.getFaxTypeDesc());

        return faxContentMap;
    }

TODO

副作用(Side Effects)

Command Query Separation

Exceptions

别出现重复代码(DRY – Don’t Repeat Yourself)