[解鎖新姿式] 兄dei,你代碼須要優化了

黑客(程序員)也是創做者,與畫家、建築師、做家同樣。 ——《黑客與畫家》java

前言

在咱們日常開發過程當中,因爲項目時間緊張,代碼能夠用就好,每每會忽視代碼的質量問題。甚至有些複製粘貼過來,不加以整理規範。每每致使項目後期難以維護,更別說後續接手項目的人。因此啊,咱們要編寫出優雅的代碼,方便你我他,豈不美哉?程序員

下面分享一些我在開發中經常使用的編碼中小建議,若有不妥,歡迎你們一塊兒交流學習。ide

衛語句

衛語句,就是把複雜的條件表達式拆分紅多個條件表達式。好比 多個 if-elseif-else 嵌套, 能夠拆分紅多個 if。以下面代碼函數

代碼:

-------------------- before  --------------------

public void today() {
    if (isWeekend()) {
        if (isFee()) {
            System.out.println("study Android");
        } else {
            System.out.println("play a game");
        }
    } else {
        System.out.println("go to work");
    }
}


 -------------------- after  (建議) --------------------

public void today() {

    // 提早過濾掉`特殊狀況`
    if (!isWeekend()) {
        System.out.println("go to work");
        return; // 提早return
    }

    //提早過濾掉`特殊狀況`
    if (isFee()) {
        System.out.println("study Android");
        return; // 提早return
    }

    // 更關注於 `核心業務`代碼實現。
    System.out.println("play a game");
}
複製代碼

提早過濾掉特殊狀況,更關注核心業務邏輯學習

小函數

咱們日常開發的時候,應該編寫小而美函數,避免函數過長。通常函數最好在15行之內(建議) 咱們看看下面代碼:優化

-------------------- before  --------------------

if (age > 0 && age < 18){
    System.out.println("小孩子");
}

if (number.length() == 11){
    System.out.println("符合手機號");
}

-------------------- after (建議) --------------------

private static boolean isChild(int age) {
    return age > 0 && age < 18;
}

private static boolean isPhoneNumber(String number) {
    return number.length() == 11;
}

if (isChild(age)){
    System.out.println("小孩子");
}

if (isPhoneNumber(number)){
    System.out.println("符合手機號");
}
複製代碼

把判斷語句抽取成一個個小函數, 這樣代碼更加清晰明瞭。this

迪米特法則

概念:

迪米特法則(Law of Demeter)又叫做最少知識原則(Least Knowledge Principle 簡寫LKP),就是說一個對象應當對其餘對象有盡可能少瞭解。例如 當一條語句中 一個對象出現兩個 .student.getName().equals("張三")) 就是代碼壞味道的表現,以下代碼所示。編碼

代碼:

-------------------- before  --------------------

public class Student {

    private String name;

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

public static void main(String[] args) {

    Student student = new Student("張三");

    // 注意看這裏,
    // 這裏獲取 student的name屬性,在根據name屬性進行判斷
    if (student.getName().equals("張三")) {
        System.out.println("個人好朋友是 " + student.getName());
    }
}


 -------------------- after (建議) --------------------
 
 public class Student {

    ... 省略name代碼

    // 新增一個 判斷是不是個人好朋友方法
    public boolean isGoodFriend(){
        return this.name.equals("張三");
    }
}

public static void main(String[] args) {

    Student student = new Student("張三");

    // 根據迪米特法則,把判斷邏輯,抽取到 Student 內部,暴露出方法(isGoodFriend)
    if (student.isGoodFriend()){
        System.out.println("個人好朋友是 " + student.getName());
    }
}
複製代碼

IDEA/Android Studio 抽取方法快捷鍵: option + command + Mspa

Map 提取對象

咱們在日常開發中,會使用到map,可是在面向對象開發理念中,一個 map的使用,每每就會錯過了 Java Bean。建議使用 Java Bean 更直觀。以下代碼:設計

public static void main(String[] args) {

    -------------------- before  --------------------
        Map<String, String> studentMap = new HashMap<>();
        studentMap.put("張三", "男");
        studentMap.put("小紅", "女");
        studentMap.put("李四", "男");

        studentMap.forEach((name, sex) -> {
            System.out.println(name + " : " + sex);
        });

    -------------------- after (建議)  --------------------
    
        List<Student> students = new ArrayList<>();
        students.add(new Student("張三", "男"));
        students.add(new Student("小紅", "女"));
        students.add(new Student("李四", "男"));

        for (Student student : students) {
            System.out.println(student.getName() + ":" + student.getSex());
        }
    }
複製代碼

筆者在編寫這點時候,有所顧慮。確定有小夥伴跳出來講,mapbean 不是同樣嗎?用map 我還能夠省去思考如何命名Class呢。可是從代碼規範來講,這樣代碼設計不是更符合 Java 面向對象的思想嗎?

Stream

Java 8 API添加了一個新的抽象稱爲流Stream,可讓你以一種聲明的方式處理數據。使得代碼調用起來更加優雅~ 直接來看代碼:

public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("張三", "男"));
        students.add(new Student("李四", "男"));
        students.add(new Student("小紅", "女"));
        students.add(new Student("小花", "女"));
        students.add(new Student("小紅", "女"));
        
        -------------------- before  --------------------
        //統計男生個數
        //傳統的 for each 循環遍歷
        long boyCount = 0;
        for (Student student : students) {
            if (student.isBoy()) {
                boyCount++;
            }
        }

        System.out.println("男生個數 = " + boyCount);

        -------------------- after (建議)  --------------------

        //統計男生個數
        //stream 流遍歷
        long count = students.stream()
                .filter(Student::isBoy) // 等同於.filter(student -> student.isBoy())
                .count();
                
        System.out.println("男生個數 = " + boyCount);
}
複製代碼

相比與 傳統的 For 循環,更推薦你們使用 stream 遍歷。 stream 流的鏈式調用,還有許多騷操做,如 sorted, map, collect等操做符,能夠省去沒必要要if-elsecount等判斷邏輯。

多態

Java 三大特性之一,多態,相信你們都不會陌生,多態的好處就是根據對象不一樣類型採起不一樣的的行爲。咱們經常在編寫 switch 語句的時候,若是改用多態,能夠把每一個分支,抽取到一個子類內的覆寫函數中,這就更加靈活。

咱們有這樣一個需求,編寫一個簡單計算器方法,咱們先來看一小段代碼:

-------------------- before  --------------------
    public static int getResult(int numberA, int numberB, String operate) {
        int result = 0;
        switch (operate) {
            case "+":
                result = numberA + numberB;
                break;
            case "-":
                result = numberA - numberB;
                break;
            case "*":
                result = numberA * numberB;
                break;
            case "/":
                result = numberA / numberB;
                break;
        }
        return result;
    }
    
    -------------------- after (建議)  --------------------
    
    abstract class Operate {
        abstract int compute(int numberA, int numberB);
    }
    
    class AddOperate extends Operate {

        @Override
        int compute(int numberA, int numberB) {
            // TODO 在這裏處理相關邏輯
            return numberA + numberB;
        }
    }
    
    ... SubOperate, MulOperate, DivOperate 也和 AddOperate同樣這裏就不一一貼出
    
    public static int getResult(int numberA, int numberB, String operate) {
        int result = 0;
        switch (operate) {
            case "+":
                result = new AddOperate().compute(numberA, numberB);
                break;
            case "-":
                result = new SubOperate().compute(numberA, numberB);
                break;
            case "*":
                result = new MulOperate().compute(numberA, numberB);
                break;
            case "/":
                result = new DivOperate().compute(numberA, numberB);
                break;
        }
        return result;
    }
複製代碼

有小夥伴可能會說,你這不是更復雜了嗎?

對比起單純的switch,咱們能夠這樣理解:

  • 雖然在類上有所增長,可是經過多態,把對應操做的邏輯分離出來,使得代碼耦合度下降。
  • 若是要修改對應加法的邏輯, 咱們只須要修改對應 AddOperate類就能夠了。避免直接修改getResult 方法
  • 代碼可讀性更好,語義更加明確。

可是這裏會存在一些問題,若是咱們新增一個平方根平方等計算方式, 就須要修改 switch 裏面的邏輯,新增一個條件分支。下面咱們再來看看更進一步的優化。

反射

經過上面例子,咱們能夠進一步優化,經過反射生成對應的 Class,而後在調用compute方法。以下代碼:

public static <T extends Operate> int getResult(int numberA, int numberB, Class<T> clz) {
        int result = 0;
        try {
            return clz.newInstance().compute(numberA, numberB);
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return result;
        }
}


public static void main(String[] args) {
    // 調用的時候直接傳遞 class 便可
    System.out.println(getResult(1, 2, SumOpearte.class));
}
複製代碼

根據傳入 class 參數,而後生成對應 Opearte處理類, 對比多態方式,咱們這裏採用反射,使得代碼耦合度大大下降,若是在增長平方根平方等計算方式。咱們只須要 新增一個 class 繼承 Opearte 便可,getResult 不用作任何修改。

須要注意的是,不是全部switch語句都須要這樣替換, 在面對簡單的 switch語句,就沒必要要了, 避免過分設計的嫌疑。以下代碼:

public String getResult(int typeCode) {
        String type = "";
        switch (typeCode) {
            case 0:
                type = "加法";
                break;
            case 1:
                type = "減法";
                break;
            case 2:
                type = "乘法";
                break;
            case 3:
                type = "觸發";
                break;
        }
        return type;
}
複製代碼

最後

以上就是我在編碼上的一些小建議,若有不妥,歡迎你們一塊兒交流學習。

相關文章
相關標籤/搜索