跟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();
}
這裏我們定義了我們需要兩個字段: name
和numberOfLegs
以及一個用來構建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。還是那句話,凡是能自動化檢測的東西不要依靠人肉。這裏涉及到的主要技術是:
- 通過掃描classpath對指定包裏麵所有類的方法,參數,返回值進行檢查。
-
hamcrest
這個支持matcher
的單元測試的庫,它讓你不隻可以assertTrue
,assertFalse
,assertEquals
, 而是可以自由指定需要滿足的條件。
總結
給Beam貢獻代碼的時候最大感受在於,你不需要去問其它Commiter或者看什麼文檔去確認你的代碼風格是否符合,是否用對了Maven版本,JDK的版本對不對,JavaDoc需不需要寫等等,你隻要編譯下代碼,maven會告訴你的代碼是否OK。隻要通過了編譯,代碼風格、一些小的錯誤等等基本不會有了,那麼代碼review的時候主要就集中在代碼設計層麵了。有用的maven插件還有很多,這裏隻是舉了幾個例子,我覺得比每個插件本身更重要的是:
- 對代碼質量的重視的意識。
- 使用工具/代碼(而不是文檔、流程)來解決保障代碼質量。
最後更新:2017-06-30 12:02:08