1. 程式人生 > >如何成為更好的程式設計師?

如何成為更好的程式設計師?

閱讀本文並瞭解如何使用具有功能組合的宣告性程式碼成為更好的程式設計師。

在許多情況下,具有功能組合的宣告性解決方案提供優於傳統命令式程式碼的程式碼度。閱讀本文並瞭解如何使用具有功能組合的宣告性程式碼成為更好的程式設計師。

在本文中,我們將仔細研究三個問題示例,並研究兩種不同的技術(命令式和宣告性)來解決這些問題。

本文中的所有原始碼都是開源的,可從https://github.com/minborg/imperative-vs-declarative獲取。最後,我們還將看到本文的學習如何應用於資料庫應用程式領域。我們將使用Speedment Stream作為ORM工具,因為它提供了與資料庫中的表,檢視和連線相對應的標準Java Streams,並支援宣告性構造。

實際上有無數個候選示例可用於程式碼度量評估。

1.問題示例

在本文中,我選擇了開發人員在日常工作可能遇到的三個常見問題:

1.1.SumArray

迭代陣列並執行計算

1.2.GroupingBy

並行聚合值

1.3.Rest

使用分頁實現REST介面

2.解決方案技術

正如本文開頭所描述的,我們將使用這兩種編碼技術解決問題:

2.1 命令式解決方案

一個命令式的解決方案,我們使用帶有for迴圈和顯式可變狀態的傳統程式碼樣例。

2.2 宣告式解決方案

宣告式解決方案,其中我們組合各種函式以形成解決問題的高階複合函式,通常使用java.util.stream.Stream或其變體。

3.程式碼指標

然而,我們的想法是使用SonarQube(此處為SonarQube Community Edition,Version 7.7)將靜態程式碼分析應用於不同的解決方案,以便我們可以為問題/解決方案組合推匯出有用且標準化的程式碼度量標準。然後將比較這些指標。

在本文中,我們將使用以下程式碼度量標準:

3.1. LOC

“LOC”表示“程式碼行”,是程式碼中非空行的數量。

3.2. Statements

是程式碼中的語句總數。每個程式碼行上可能有零到多個語句。

3.3. 迴圈複雜性

表示程式碼的複雜性,並且是通過原始碼程式的線性獨立路徑數量的定量度量。例如,單個“if”子句在程式碼中顯示兩條單獨的路徑。在維基百科上閱讀更多內容。

3.4。認知複雜性

SonarCube聲稱:

“認知複雜性改變了使用數學模型來評估軟體可維護性的實踐。它從Cyclomatic Complexity設定的先例開始,但是使用人為判斷來評估結構應該如何計算,並決定應該將什麼新增到模型中作為一個整體結果,它產生了方法複雜性分數,這使得程式設計師對可維護性模型的評估比以前更公平。“

在SonarCube自己的頁面上可以閱讀更多內容。

通常情況下,需要設想一個解決方案,其中這些指標很小而不是很大。

對於記錄,應該注意下面設計的任何解決方案只是解決任何給定問題的一種方法。如果您知道更好的解決方案,請隨時通過https://github.com/minborg/imperative-vs-declarative拉取請求提交意見。

4.迭代陣列

我們從簡單開始。此問題示例的物件是計算int陣列中元素的總和,並將結果返回為long。以下介面定義了問題:

public interface SumArray {
    long sum(int[] arr);
}

4.1.命令式解決方案

以下解決方案使用命令式技術實現SumArray問題:

public class SumArrayImperative implements SumArray {
    @Override
    public long sum(int[] arr) {
        long sum = 0;
        for (int i : arr) {
            sum += i;
        }
        return sum;
    }
}

4.2宣告式解決方案

這是一個使用宣告性技術實現SumArray的解決方案:

public class SumArrayDeclarative implements SumArray {
    @Override
    public long sum(int[] arr) {
        return IntStream.of(arr)
            .mapToLong(i -> i)
            .sum();
    }
}

請注意,IntStream :: sum只返回一個int,因此,我們必須加入中間操作mapToLong()。

4.3.分析

SonarQube提供以下分析:

SumArray的程式碼度量標準如下表所示(通常更低):

技術 LOC Statements 迴圈複雜性 認知複雜性
Imperative 12 5 2 1
Functional 11 2 2 0

這是它在圖表中的值(通常更低):

5.並行聚合值

這個問題示例的物件是將Person物件分組到不同的桶中,其中每個桶構成一個人的出生年份和一個人工作的國家的唯一組合。對於每個組,應計算平均工資。聚合應使用公共ForkJoin池平行計算。

這是(不可變的)Person類:

public final class Person {
    private final String firstName;
    private final String lastName;
    private final int birthYear;
    private final String country;
    private final double salary;
    public Person(String firstName, 
                  String lastName, 
                  int birthYear, 
                  String country, 
                  double salary) {
        this.firstName = requireNonNull(firstName);
        this.lastName = requireNonNull(lastName);
        this.birthYear = birthYear;
        this.country = requireNonNull(country);
        this.salary = salary;
    }
    public String firstName() { return firstName; }
    public String lastName() { return lastName; }
    public int birthYear() { return birthYear; }
    public String country() { return country; }
    public double salary() { return salary; }
    // equals, hashCode and toString not shown for brevity
}

我們還定義了另一個名為YearCountry的不可變類,把它作為分組鍵:

public final class YearCountry {
    private final int birthYear;
    private final String country;
    public YearCountry(Person person) {
        this.birthYear = person.birthYear();
        this.country = person.country();
    }
    public int birthYear() { return birthYear; }
    public String country() { return country; }
    // equals, hashCode and toString not shown for brevity
}

定義了這兩個類之後,我們現在可以通過介面定義此問題示例:

public interface GroupingBy {
    Map<YearCountry, Double> average(Collection<Person> persons);
}

5.1.命令式的解決方案

實現GroupingBy示例問題的命令式解決方案並非易事。這是問題的一個解決方案:

public class GroupingByImperative implements GroupingBy {
    @Override
    public Map<YearCountry, Double> average(Collection<Person> persons) {
        final List<Person> personList = new ArrayList<>(persons);
        final int threads = ForkJoinPool.commonPool().getParallelism();
        final int step = personList.size() / threads;
        // Divide the work into smaller work items
        final List<List<Person>> subLists = new ArrayList<>();
        for (int i = 0; i < threads - 1; i++) {
           subLists.add(personList.subList(i * step, (i + 1) * step));
        }
        subLists.add(personList.subList((threads - 1) * step, personList.size()));
        final ConcurrentMap<YearCountry, AverageAccumulator> accumulators = new ConcurrentHashMap<>();
        // Submit the work items to the common ForkJoinPool
        final List<CompletableFuture<Void>> futures = new ArrayList<>();
        for (int i = 0; i < threads; i++) {
            final List<Person> subList = subLists.get(i);
       futures.add(CompletableFuture.runAsync(() -> average(subList, accumulators)));
        }
        // Wait for completion
        for (int i = 0; i < threads; i++) {
            futures.get(i).join();
        }
        // Construct the result
        final Map<YearCountry, Double> result = new HashMap<>();
        accumulators.forEach((k, v) -> result.put(k, v.average()));
        return result;
    }
    private void average(List<Person> subList, ConcurrentMap<YearCountry, AverageAccumulator> accumulators) {
        for (Person person : subList) {
            final YearCountry bc = new YearCountry(person);
          accumulators.computeIfAbsent(bc, unused -> new AverageAccumulator())
                .add(person.salary());
        }
    }
    private final class AverageAccumulator {
        int count;
        double sum;
        synchronized void add(double term) {
            count++;
            sum += term;
        }
        double average() {
            return sum / count;
        }
    }
}

5.2. 宣告式解決方案

這是一個使用宣告性構造實現GroupingBy的解決方案:

public class GroupingByDeclarative implements GroupingBy {
    @Override
    public Map<YearCountry, Double> average(Collection<Person> persons) {
        return persons.parallelStream()
            .collect(
             groupingBy(YearCountry::new, averagingDouble(Person::salary))
            );
    }
}

在上面的程式碼中,我使用了一些來自Collectors類的靜態匯入(例如Collectors :: groupingBy)。這不會影響程式碼指標。

5.3.分析

SonarQube提供以下分析:

GroupingBy的程式碼度量標準如下表所示(通常更低):

技術 LOC Statements 迴圈複雜性 認知複雜性
Imperative 52 27 11 4
Functional 17 1 1 0

這是它在圖表中的值(通常更低):

6.實現REST介面

在該示例性問題中,我們將為Person物件提供分頁服務。出現在頁面上的Persons必須滿足某些(任意)條件,並按特定順序排序。該頁面將作為不可修改的Person物件列表返回。

這是一個解決問題的介面:

public interface Rest {

/**

 * Returns an unmodifiable list from the given parameters.
 *
 * @param persons as the raw input list
 * @param predicate to select which elements to include
 * @param order in which to present persons
 * @param page to show. 0 is the first page
 * @return an unmodifiable list from the given parameters
 */

 List<Person> page(List<Person> persons, 
                   Predicate<Person> predicate,
                   Comparator<Person> order,
                   int page);
}

頁面的大小在名為RestUtil的單獨工具程式類中:

public final class RestUtil {
    private RestUtil() {}
    public static final int PAGE_SIZE = 50;
}

6.1.命令式實現方法

public final class RestImperative implements Rest {
    @Override
    public List<Person> page(List<Person> persons, 
                Predicate<Person> predicate, 
                  Comparator<Person> order, 
                             int page) {
        final List<Person> list = new ArrayList<>();
        for (Person person:persons) {
            if (predicate.test(person)) {
                list.add(person);
            }
        }
        list.sort(order);
        final int from = RestUtil.PAGE_SIZE * page;
        if (list.size() <= from) {
            return Collections.emptyList();
        }
        return unmodifiableList(list.subList(from, Math.min(list.size(), from + RestUtil.PAGE_SIZE)));
    }
}

6.2.宣告式解決方法

public final class RestDeclarative implements Rest {
    @Override
    public List<Person> page(List<Person> persons,
                      Predicate<Person> predicate, 
                        Comparator<Person> order,
                             int page) {
        return persons.stream()
            .filter(predicate)
            .sorted(order)
            .skip(RestUtil.PAGE_SIZE * (long) page)
            .limit(RestUtil.PAGE_SIZE)
           .collect(collectingAndThen(toList(), Collections::unmodifiableList));
    }
}

6.3.分析

SonarQube提供以下分析:

Rest的程式碼度量標準如下表所示(通常更低):

技術 LOC Statements 迴圈複雜性 認知複雜性
Imperative 27 10 4 4
Functional 21 1 1 0

這是它在圖表中的值(通常更低):

7.Java 11改進

上面的例子是用Java 8編寫的。使用Java 11,我們可以使用LVTI(區域性變數型別推斷)縮短宣告性程式碼。這會使我們的程式碼更短,但不會影響程式碼指標。

@Override
public List<Person> page(List<Person> persons,
                         Predicate<Person> predicate, 
                         Comparator<Person> order, 
                         int page) {
    final var list = new ArrayList<Person>();
    ...

與Java 8相比,Java 11包含一些新的收集器。例如,Collectors.toUnmodifiableList(),它將使我們的宣告性Rest解決方案更短:

public final class RestDeclarative implements Rest {
@Override
public List<Person> page(List<Person> persons,
                         Predicate<Person> predicate, 
                         Comparator<Person> order, 
                         int page) {
    return persons.stream()
        .filter(predicate)
        .sorted(order)
        .skip(RestUtil.PAGE_SIZE * (long) page)
        .limit(RestUtil.PAGE_SIZE)
        .collect(toUnmodifiableList());
}

同樣,這不會影響程式碼指標。

8.摘要

三個示例性問題的平均程式碼度量產生以下結果(通常更低):

鑑於本文中的輸入要求,當我們從命令式構造到宣告式構造時,所有程式碼度量標準都有顯著改進。

8.1.在資料庫應用程式中使用宣告性構造

為了在資料庫應用程式中獲得宣告性構造的好處,我們使用了Speedment Stream。 Speedment Stream是一個基於流的Java ORM工具,可以將任何資料庫表/檢視/連線轉換為Java流,從而允許您在資料庫應用程式中應用宣告性技能。

您的資料庫應用程式程式碼將變得更好。事實上,針對資料庫的Speedment和Spring Boot的分頁REST解決方案可能表達如下:

public Stream<Person> page(Predicate<Person> predicate, 
                     Comparator<Person> order, 
                           int page) {
    return persons.stream()
        .filter(predicate)
        .sorted(order)
        .skip(RestUtil.PAGE_SIZE * (long) page)
        .limit(RestUtil.PAGE_SIZE);
}

Manager<Person> persons由Speedment提供,並構成資料庫表“Person”的控制代碼,可以通過Spring使用@AutoWired註解。

9.總結

選擇宣告性命令式解決方案可以大大降低一般程式碼複雜性,並且可以提供許多好處,包括更快的編碼,更好的程式碼質量,更高的可讀性,更少的測試,更低的維護成本等等。

為了從資料庫應用程式中的宣告性構造中受益,Speedment Stream是一種可以直接從資料庫提供標準Java Streams的工具。

掌握宣告性構造和功能組合是當今任何當代Java開發人員必須的。


8月福利準時來襲,關注公眾號

後臺回覆:003即可領取7月翻譯集錦哦~

往期福利回覆:001,002即可領取!

相關推薦

6條成為程式設計師的建議

1、研究並持續改進吸收,不僅僅是學習。對自己做過的專案,功能模組,聯絡進行分析和優化。學習技術是不可或缺的,不斷對技術和做過的專案保持精益求精的態度,更加重要。 2、保持慣性。有規律的工作習慣,和每週至少3小時的運動量。至少當前一週,一天的工作要有明晰的目標。儘可能每1.5小時活動

成為程式設計師的8種途徑

譯者注:本文作者講述了8種方式幫助你如何從一名普通的程式設計師進階成為一名偉大的程式設計師,讓我們就從此時此刻開始提高自己的開發技能吧。以下為譯文。是時候開始認真考慮一下如何升級你的開發技術了。讓我們來認真地學習一下吧。給自己設定一個提高開發技術的目標很容易,但

專訪Josh Wills:從數學到程式設計,如何成為程式設計師

​​鑑於在少年時代著迷於微積分,長大後的Josh Wills前往杜克大學選修了理論數學專業。在大學的最後一年,他認識了統計學這樣一個學科,雖然比起偏微分方程,Josh更喜歡後者,但他確實在那一刻起就喜歡上了這一個學科。 在那之後,Josh去過IBM一小段時間,然後去得州大學奧斯汀分校成為一名

如何成為程式設計師

       最近在我的社交圈子裡出現了關於“更為更好的程式設計師”的方法的討論。基於這場討論,我決定與大家分享一下自己的更為更好的程式設計師的方法。我希望大家知道,我發現的方法經實踐證明是有用的,所以大家也可以將它們用到自己的生活中。  &nb

讓我們成為程式設計師

即使是很聰明的程式設計師也存在很大的成長空間,那麼現在,我們就來談談如何做個更有想法、更善於突破自我的程式設計師。 1.善於總自身找原因 在發生錯誤之後,要學會首先質疑自己和他人的預設情況,因為來自不同的供應商可能存在 內建不同的預設。當有人想你報告一個你無法重複的問題

五個方法成為程式設計師 .

  對我來說,一個好的程式設計師應該是努力去追求儘可能無錯的高質量的符合需求的程式碼實現。 一些人也許認為好的程式設計師是那些懂得多門程式語言,懂得很牛技術的程式設計師,是的,這在某些情況下是對的。但歸根到底,無論你用什麼樣的技術,什麼樣的語言,所有的程式被寫出來,其功能都

如何成為程式設計師

閱讀本文並瞭解如何使用具有功能組合的宣告性程式碼成為更好的程式設計師。 在許多情況下,具有功能組合的宣告性解決方案提供優於傳統命令式程式碼的程式碼度。閱讀本文並瞭解如何使用具有功能組合的宣告性程式碼成為更好的程式設計師。 在本文中,我們將仔細研究三個問題示例,並研究兩種不同的技術(命令式和宣告性)來解決這些問

《自信力~成為的自己》晨讀筆記

是否 bsp 是什麽 自己的 後者 語言 放松 付出 管理 自信不是紙上談兵而需要身體力行。① 『彰顯自信的習慣』讓充滿自信的肢體語言成為習慣,你也會變得更自信,這在心理學上叫做「回溯理性原則」。人的大腦願意相信那些與信念一致的行為方式,所以一旦你在舉手投足

成為的自己

好的 空間 最好 而且 發生 公開課 沒有 自己 事情 幸福公開課裏說 Be all you can be 成為最好的自己 李開復寫過一本書叫《做最好的自己》 許久前看過的 只記得倆個字 自信 自信的重要性 事實上 我們也只能成為自己 自己做了尷尬的事兒 遇到因別人的過錯而

程式設計師的標準

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Web前端如日中天,成為優秀前端程式設計師的5個祕訣,你用了幾個?

近些年,越來越多的程式設計師轉移陣地,搞起前端開發。 有不少的Android開發的程式設計師直接轉到了前端開發。 而就拿JavaScript來說,就因前端流行使得它在各種程式語言排行榜上名列前茅。 那麼,如何做一名優秀、甚至卓越的WEB前端工程師? 雖然說小夥伴們的學習技巧各有差異,

成為Java頂尖程式設計師 ,要看這11本書

轉載地址:http://www.kuqin.com/shuoit/20160107/349896.html 學習的最好途徑就是看書“,這是我自己學習並且小有了一定的積累之後的第一體會。 個人認為看書有兩點好處:          &nbs

成為Java頂尖程式設計師,先過了下面問題!

一、資料結構與演算法基礎 說一下幾種常見的排序演算法和分別的複雜度。 用Java寫一個氣泡排序演算法 描述一下鏈式儲存結構。 如何遍歷一棵二叉樹? 倒排一個LinkedList。 用Java寫一個遞迴遍歷目錄下面的所有檔案。 二、Java基礎 介面與抽象類的區

一個程式設計師要扔掉多少程式碼,才能成為真正的程式設計師

俗語說,女怕嫁錯郎,男怕入錯行。 程式設計師這行當,不貧不富,靠技術吃飯,一直還算個體面職業。進了程式設計師這行的兄弟們,即便日後飛黃騰達了,談起早年的程式設計生涯,都還是自豪的。 不過呢,程式設計師這個職業,有個挺悲哀的地方,那就是無效工作的佔比極高。 直接點說,程式設計師辛辛

所以你根本不想成為一名程式設計師

我收到過很多來自職業程式設計師的郵件,他們在這個行業工作了一段時間,最終決定不幹這行了。最近收到這封: 我去年獲得了電腦科學學位,做了一年Java EE。我大學裡對需求工程和“管理知識”更興趣,但我們總要面對這樣一個事實:你往往被驅使成為一名程式設計師。 我喜歡程式設計本身。我做得不錯,我甚至比一

成為Java頂尖程式設計師 ,看這9本書就夠了

“學習的最好途徑就是看書”,這是我自己學習並且小有了一定的積累之後的第一體會。個人認為看書有兩點好處: 1.能出版出來的書一定是經過反覆的思考、雕琢和稽核的,因此從專業性的角度來說,一本好書的價值遠超其他資料 2.對著書上的程式碼自己敲的時候方便 “看

作為程式設計師,一定要加班才是程式設計師嗎?

當你看到這個題目時可能會感到很驚訝,因為不同的人,可能有不同的看法。如果你是一個職場高手,從事軟體開發多年,對於工作上的問題能夠很快的解決,別說需要加班,可能就半天的時間,就能夠做好自己的工作,剩下的很多寶貴的時間就是可以自由安排了。但是對於一個剛剛加入IT行業的新人來說,

推薦《Clean Code》一書,讓你成為優秀的程式設計師

曾經維護過有十多年曆史的銀行系統,也全新開發過多模組的企業應用系統。經歷過各種各樣寫法的程式碼,有的難以維護,有的清晰明瞭,有的埋下深坑… 在我的團隊裡,我總是向新人灌輸程式碼整潔之道的思想,我時刻告訴他們,功能完成只是最基本的要求,更重要的是你能把程式碼

2019 年,19 種方法讓自己成為的 Node.js 工程師

原文作者:Yoni Goldberg 譯者:UC 國際研發 Jothy 寫在最前:歡迎你來到“UC國際技術”公眾號,我們將為大家提供與客戶端、服務端、演算法、測試、資料、前端等相關的高質量技術文章,不限於原創與翻譯。 編者按:文中作者為大家提供了19種方法,大多數方法後面都提供了例子,如果你對這些例

通過「刻意練習」,你才能成為頂尖的程式設計師

作家格拉德威爾在《異類》一書中指出: 人們眼中的天才之所以卓越非凡,並非天資超人一等,而是付出了持續不斷的努力。1萬小時的錘鍊是任何人從平凡變成超凡的必要條件。 他將此稱為「一萬小時定律」。 要成為某個領域的專家,需要10000小時,按比例計算就是:如果每天工