閱讀1020 返回首頁    go 阿裏雲 go 技術社區[雲棲]


跟ApacheBeam學質量控製之道

在學習、開發Apache Beam源碼過程中,除了它精妙的設計(通過幾個簡單的概念抽象把實時和離線的計算邏輯模型統一了起來),龐大的代碼量(Java 33萬行, Python9萬行),還有一個比較大的感受是它的質量控製做得特別好,比之前參與過的其它一些開源項目都要好,這可能跟Google的工程質量高於業界有關。但是這裏麵也沒什麼什麼奇技淫巧,隻是善用了一些插件而已,我在這裏想把我在Apache Beam裏麵看到、學到的一些質量實踐分享給大家。代碼質量提升其實沒有什麼太多的捷徑,這裏要分享的也不是多麼高大上的道理,每個小點都是很零碎的一個小技巧,但是所有這些小技巧組合起來,會讓你對代碼的質量更有信心。

我們代碼裏麵的一些常見問題

事物的好壞是對比出來的,我們先來看看我們代碼裏麵一些問題。

隨意的JavaDoc

比如:

/**
 * UserService
 *
 * @author <somebody>
 * @version $Id: UserService.java, v 0.1 2014年8月28日 上午11:04:58 <somebody> Exp $
 */
public interface UserService extends ExtUserService {

再比如:

/**
 * DataProjectService
 *
 * @author <somebody>
 * @since created on 2015-10-28 22:27
 */
public interface DataProjectService {

我們代碼很多沒有javadoc, 或者好一點的有javadoc,但是不符合javadoc規範(用javadoc實際去生成會報格式不對)。

超長的代碼

下麵截取一段CAP裏麵的代碼:

    /**
     * // 看這裏
     * @see com.alipay.cap.core.service.DataProjectAccessKeyService#updateExcuteAKById(java.lang.Long, java.lang.String, java.lang.String)
     */
    @Override
    public void updateExcuteAKByUnionKey(String namespaceId, String dataProjectKey, String accessId, String accessKey, String operator) {
        CapNamespaceProjectAkCriteria example = new CapNamespaceProjectAkCriteria();
        example.createCriteria()
            .andDataProjectKeyEqualTo(dataProjectKey)
            .andNamespaceIdEqualTo(namespaceId)
            .andIsDeletedEqualTo(false);
        List<CapNamespaceProjectAkDO> capNamespaceProjectAkDOs = capNamespaceProjectAkDAO.selectByExample(example);
        // .....
    }

比如這裏這行代碼長達138,與一般的推薦配置(80, 最多100)長多了,已經超出一屏了,看起來很難受。

重複的、未使用的maven依賴

用依賴分析工具分析一下CAP的代碼依賴,可以發現,裏麵有很多沒用的依賴以及用到了但是沒有聲明的依賴。

[INFO] --- maven-dependency-plugin:2.8:analyze (default-cli) @ cap-web-openapi ---
[WARNING] Used undeclared dependencies found:
[WARNING]    com.alipay.cap:cap-common-client:jar:1.0.20140703:compile
[WARNING]    com.alipay.cap:cap-common-conf-core:jar:1.0.20170512:compile
[WARNING]    com.alipay.cap:cap-common-dal:jar:1.1:compile
[WARNING]    com.google.code.findbugs:annotations:jar:2.0.3:compile
[WARNING]    com.alibaba.alimonitor:alimonitor-jmonitor:jar:1.0.4:compile
[WARNING]    org.slf4j:slf4j-api:jar:1.7.14:compile
[WARNING]    com.alipay.dpc:smartmw-cache:jar:2.0.20161227:compile
[WARNING]    org.springframework:org.springframework.context:jar:3.0.5.RELEASE:compile
[WARNING]    org.springframework:spring-modules-validation:jar:0.9:compile
[WARNING]    org.springframework:org.springframework.core:jar:3.0.5.RELEASE:compile
[WARNING]    com.alibaba:fastjson:jar:1.2.8.sec01:compile
[WARNING]    com.alibaba.toolkit.common:toolkit-common-lang:jar:1.1.1:compile
[WARNING]    com.alipay.sofa:sofa-runtime-api:jar:4.5.2:compile
[WARNING] Unused declared dependencies found:
[WARNING]    com.alipay.sofa.service:sofa-service-api:jar:3.2.4.1:compile
[WARNING]    com.alipay.sofa.web.mvc:mvc-sofa-env-plugin:jar:4.1.6:compile
[WARNING]    com.alipay.sofa.web.mvc:mvc-alipay-toolbox-plugin:jar:4.1.6:compile
[WARNING]    com.alipay.sofa.web.mvc:mvc-toolbox-plugin:jar:4.1.6:compile
[WARNING]    com.alipay.sofa.web.mvc:mvc-validation-plugin:jar:4.1.6:compile
[WARNING]    com.alipay.sofa.web.mvc:mvc-tair-session-plugin:jar:4.1.6:compile
[WARNING]    com.alipay.sofa.web.mvc:mvc-widget-plugin:jar:4.1.6:compile
[WARNING]    com.alipay.sofa.web.mvc:mvc-uisvr-plugin:jar:4.1.6:compile
[WARNING]    com.alipay.sofa.web.mvc:mvc-json-plugin:jar:4.1.6:compile
[WARNING]    com.alipay.sofa.web.mvc:mvc-resource-plugin:jar:4.1.6:compile
[WARNING]    com.alipay.sofa.web.mvc:mvc-alipay-security-plugin:jar:4.1.6:compile
[WARNING]    com.alipay.sofa.web.mvc:mvc-alipay-auth-plugin:jar:4.1.6:compile
[WARNING]    commons-beanutils:commons-beanutils:jar:1.7.0:compile
[WARNING]    com.alipay.cap:cap-biz-service-impl:jar:1.0:compile
[WARNING]    org.springframework:spring-webmvc:jar:3.2.2:compile

這些每個問題單個看起來都不是大問題,但是堆積起來會讓代碼看起來有點

Beam裏麵的一些小實踐

下麵我介紹一下我在Beam裏麵看到的質量控製相關的一些小技巧,主要是一些maven插件的使用和一些測試庫的使用,以及一些思想上的意識。

Dependency插件

Dependency插件的作用是幫我們掃描項目的依賴問題,它可以把我們實際要到了,但是沒有聲明的依賴;或者實際沒用到,但是聲明了的依賴都給找出來,幫你保持代碼的純潔, 下麵是一個掃描報錯的例子:

[INFO] --- maven-dependency-plugin:3.0.1:analyze-only (default) @ beam-dsls-sql ---
[WARNING] Unused declared dependencies found:
[WARNING]    com.google.auto.value:auto-value:jar:1.4.1:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 27.409 s
[INFO] Finished at: 2017-06-16T11:33:03+08:00
[INFO] Final Memory: 50M/401M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-dependency-plugin:3.0.1:analyze-only (default) on project beam-dsls-sql: Dependency problems found -> [Help 1]
[ERROR]

這裏我們聲明了一個沒有用到的,但是聲明了的auto-value的依賴,因此編譯失敗了。有了這個插件的保證,我們可以很有自信的知道我們引入的每個依賴都是有用的,有意義的。

Checkstyle

Checkstyle is a development tool to help programmers write Java code that adheres to a coding standard. It automates the process of checking Java code to spare humans of this boring (but important) task. This makes it ideal for projects that want to enforce a coding standard.

它能做的一些典型檢查包括:

  • FallThrough: 如果你的switch/case裏麵沒有寫break,它會自動檢測出來。
  • CustomImportOrder: 檢查import的順序符合指定樣式。
  • LineLength: 檢查代碼行數不要超長。
  • MethodLength: 檢查方法行數不要超長。

下麵是我最近在寫Beam代碼的時候編譯時候報的幾個Checkstyle錯誤:

[bash] There are 5 errors reported by Checkstyle 6.19 with beam/checkstyle.xml ruleset.
[ERROR] src/main/java/org/apache/beam/dsls/sql/meta/Column.java:[19] (javadoc) JavadocType: Missing a Javadoc comment.
[ERROR] src/main/java/org/apache/beam/dsls/sql/meta/DefaultMetaStore.java:[9] (javadoc) JavadocParagraph: Empty line should be followed by <p> tag on the next line.
[ERROR] src/main/java/org/apache/beam/dsls/sql/meta/Table.java:[21] (javadoc) JavadocType: Missing a Javadoc comment.
[ERROR] src/main/java/org/apache/beam/dsls/sql/meta/Table.java:[35] (javadoc) JavadocParagraph: Empty line should be followed by <p> tag on the next line.
[ERROR] src/main/java/org/apache/beam/dsls/sql/rel/BeamValuesRel.java:[60] (sizes) LineLength: Line is longer than 100 characters (found 115).
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 27.517 s
[INFO] Finished at: 2017-06-19T14:49:28+08:00
[INFO] Final Memory: 49M/478M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-checkstyle-plugin:2.17:check (default) on project beam-dsls-sql: You have 5 Checkstyle violations. -> [Help 1]

Enforcer插件

Enforcer是另一比有意思的maven插件, 它可以幫你指定你代碼所需的運行環境,比如需要的maven版本(maven不同版本之間有時候行為差異還是很大的,比如我們采雲間的代碼基本都是用3.x版本編譯的,但是開發機上默認裝的是2.x的,這樣編譯的時候就會報很奇怪的錯誤,而enforcer插件則可以把這個需求明確化,如果你的maven的版本不對,它會明確告訴你maven版本不對,而不是其它的詭異錯誤)。它能做到的一些檢查包括:

  • Maven的版本
  • JDK的版本
  • OS的版本
  • 檢查指定的屬性(property)是否存在

在我看來maven-enforcer-plugin有點像java語言裏麵assert,assert如果失敗了,說明運行環境有問題,直接失敗。

下麵是一個Maven版本不對的錯誤:

[INFO] --- maven-enforcer-plugin:1.4.1:enforce (enforce) @ beam-dsls-sql ---
[WARNING] Rule 2: org.apache.maven.plugins.enforcer.RequireMavenVersion failed with message:
Detected Maven Version: 3.1.1 is not in the allowed range [3.2,).

Google AutoValue

最近這些年函數式編程的思維開始流行起來,很多非函數式的語言也開始在語言層麵支持某些函數式的特征,比如Java 8裏麵的lambda, stream等等,Google AutoValue也是類似的目的,它的目的是讓你的POJO編程readonly的(隻有Getter), 從而實現函數式語言裏麵Immutable的特性。

它最大的貢獻在於,它讓你隻需要定義你需要的字段:

import com.google.auto.value.AutoValue;

@AutoValue
abstract class Animal {
  static Animal create(String name, int numberOfLegs) {
    // See "How do I...?" below for nested classes.
    return new AutoValue_Animal(name, numberOfLegs);
  }

  abstract String name();
  abstract int numberOfLegs();
}

這裏我們定義了我們需要兩個字段: namenumberOfLegs以及一個用來構建Animal對象的create方法,其它的則都由AutoValue自動生成,其中AutoValue_Animal就是AutoValue自動生成的類。在AutoValue_Animal裏麵,它幫我們自動實現了hashCode, equals, toString等等這些重要的方法。這些方法的特征是實現的過程基本都一樣,但是容易出錯(由於粗心), 那麼不如交給框架去自動產生。

如果你的類字段比較多,那麼AutoValue還支持Builder模式:

import com.google.auto.value.AutoValue;

@AutoValue
abstract class Animal {
  abstract String name();
  abstract int numberOfLegs();

  static Builder builder() {
    return new AutoValue_Animal.Builder();
  }

  @AutoValue.Builder
  abstract static class Builder {
    abstract Builder setName(String value);
    abstract Builder setNumberOfLegs(int value);
    abstract Animal build();
  }
}

這樣我們就可以一步一步漸進地把對象構造出來了。

Api Surface Test

所謂的API Surface是指我們一個係統暴露給另外一個係統一個SDK的時候,我們到底應該把哪些類,哪些package暴露給用戶,這個問題很重要,因為考慮向後兼容性的話,暴露的package越少,將來修改的時候,破壞向後兼容性的可能性就越小,SDK就越穩定。我們平常的時候對於這種事情可能都是通過人肉分析、review,在Beam裏麵它直接編寫成了一個單元測試:

  @Test
  public void testSdkApiSurface() throws Exception {

    @SuppressWarnings("unchecked")
    final Set<String> allowed =
        ImmutableSet.of(
            "org.apache.beam",
            "com.fasterxml.jackson.annotation",
            "com.fasterxml.jackson.core",
            "com.fasterxml.jackson.databind",
            "org.apache.avro",
            "org.hamcrest",
            // via DataflowMatchers
            "org.codehaus.jackson",
            // via Avro
            "org.joda.time",
            "org.junit");

    assertThat(
        ApiSurface.getSdkApiSurface(getClass().getClassLoader()), containsOnlyPackages(allowed));
  }

這個測試表明,Beam的SDK向外暴露的package就是上麵列出的這些,下次來個新手貢獻的代碼如果破壞了這個約定,那麼這個單元測試就直接報錯,無法提交merge。還是那句話,凡是能自動化檢測的東西不要依靠人肉。這裏涉及到的主要技術是:

  1. 通過掃描classpath對指定包裏麵所有類的方法,參數,返回值進行檢查。
  2. hamcrest這個支持matcher的單元測試的庫,它讓你不隻可以assertTrue, assertFalse, assertEquals, 而是可以自由指定需要滿足的條件。

總結

給Beam貢獻代碼的時候最大感受在於,你不需要去問其它Commiter或者看什麼文檔去確認你的代碼風格是否符合,是否用對了Maven版本,JDK的版本對不對,JavaDoc需不需要寫等等,你隻要編譯下代碼,maven會告訴你的代碼是否OK。隻要通過了編譯,代碼風格、一些小的錯誤等等基本不會有了,那麼代碼review的時候主要就集中在代碼設計層麵了。有用的maven插件還有很多,這裏隻是舉了幾個例子,我覺得比每個插件本身更重要的是:

  1. 對代碼質量的重視的意識。
  2. 使用工具/代碼(而不是文檔、流程)來解決保障代碼質量。

最後更新:2017-06-30 12:02:08

  上一篇:go  Java基礎入門 - 變量
  下一篇:go  Stack Clash 漏洞正粉碎 Linux 防禦危及 root 權限