> For the complete documentation index, see [llms.txt](https://cmmobile.gitbook.io/androidbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cmmobile.gitbook.io/androidbook/xin-ren-xun-lian/code-style.md).

# Coding Style

### 前言

Android Coding Style 包含以下

* Java Coding Style
* Kotlin Coding Style
* Android各種命名規則
* Android 需要遵守的規則

這邊程式碼範例會以Kotlin的語法來做說明，由於Java與Kotlin的CodingStyle規則基本上相同。所以，只有當Java 部分需要特別解釋時，才會補上Java範例。

PS.Kotlin在Android平台上會轉譯成Java，如果有會Java但不懂Kotlin的，可以在Android Studio工具列上點擊 Tools -> Kotlin -> Show Kotlin ByteCode ， 在Kotlin ByteCode上點擊 Decompile 就會顯示Java版本的程式碼

### 命名

* 程式碼除了註解與字串外，不得使用中文字
  * UnitTest或AndroidTest的方法名稱可以使用中文以方便辨識功能
* 禁用region\*新增範例 但是別這樣寫

```kotlin
// region 說明文字
你的程式碼
// endregion
```

* 所有 MagicNumber 一律宣告 常數 或 變數 或 enum(列舉) 或 seal class(for kotlin)，禁止直接在程式碼內使用

Don't

```java
for(int i = 0; i < 10; i++) {
    ...
}
```

Do

```java
final int MAX_COUNT = 10;
for(int i = 0; i < MAX_COUNT; i++) {
    ...
}
```

* 所有要對外的欄位:
  * Java一律使用屬性的方式，禁止直接使用 public 變數公開
  * Kotlin直接使用變數()。沒有必要公開的屬性就不該公開
* 禁用無大刮號單行else (DanglingElse)
* 你不該交出 Compile 後有一堆警告的程式碼，更不該交出有 Error 的程式碼
* Java變數使用小駝峰命名，private變數不需要m開頭
* kotlin變數使用小駝峰命名
* 常數成員一律 全大寫配底線分隔 => VERSION\_NUMBER
* 在命名時，請不要自己縮寫單字來命名，能用的縮寫只有大家常用的縮寫。 例如：`ResponseMessage` 縮寫成 `RspMsg` 上面的`Message` 縮寫成`Msg`，大多數人都還看得懂。但是將`Responses`縮寫成`Rsp`，如果不看原本的樣子，基本上是沒辦法看懂的，所以如果要用縮寫，只有像是`Msg`這樣大家都可以懂的縮寫可以用，不然請好好的把原本的單字寫出來。 另外，使用縮寫時大小寫不照原縮寫，應使用大寫開頭+小寫的方式，即使是像`iOS`這樣，大多數人習慣第一個`i`小寫的，也請這樣寫`IosVersionSetting`，而不是`iOSVersionSetting`。

以下針對不同地方的命名做詳細規則的說明

#### 檔案的命名

* 檔案名以大駝峰式命名。例如：NumberExtension.kt,NumberExt.kt
* 檔案名稱與類別同名 正確寫法：

```kotlin
// 檔名：MyClass.kt
class MyClass {
}
```

錯誤寫法：

```kotlin
// 檔名：MyClass.kt
class OtherClass {
}
```

* 原則上一個檔案中只有一個主要類別 例外:非泛用類別可以直接寫在類別裡面 正確寫法：

```kotlin
// 檔名：FirstClass.kt
class FirstClass {
}
```

```kotlin
// 檔名：SecondClass.kt
class SecondClass {
}
```

* 錯誤寫法：

```kotlin
// 檔名：FirstClass.kt
class FirstClass {
}
class SecondeClass {
}
```

#### 擴充方法檔案命名

* 檔名以擴充的物件命名加上Extension、Ext為後綴。例如：NumberExtension.kt,NumberExt.kt
* 檔案名以大駝峰式命名。例如：NumberExtension.kt,NumberExt.kt,NumberUtils.kt Extension寫法與用法 工具類方法檔案命名
* 檔名以目標功能命名加上Helper、Utils為後綴，例如:ChartCalculateHelper.kt, DateTimeFormatUtils.kt Helper與Utils的差別 [參考](https://stackoverflow.com/questions/12192050/what-are-the-differences-between-helper-and-utility-classes)
* 檔名以大駝峰式命名

#### 類別的命名

* 大寫駝峰式命名 正確寫法：

```kotlin
class Setting {
}
```

錯誤寫法：

```kotlin
class setting {
}
```

* 名詞 或 形容詞+名詞 正確寫法：

```kotlin
class NestedStucture {
}
```

錯誤寫法：

```kotlin
class CalculateStock {
}
```

* 類別繼承Android原生類別時在名稱尾部加上原生類別的名稱，需要寫的有Application,Activity,Fragment,Service,Adapter,ViewHolder。
* 正確寫法：

```kotlin
class ArticleViewHolder(cell: ConstraintLayout) : RecyclerView.ViewHolder(cell) {
    ...
}
```

錯誤寫法：

```kotlin
class ArticleCell(cell: ConstraintLayout) :RecyclerView.ViewHolder(cell) {
    ...
}
```

#### 方法的命名

* 使用小駝峰命名
* 使用 動詞(load, fetch, preLoad, ...) 或 動詞+名詞(getData, fetchFileList, ...) 作為命名

```kotlin
fun loadData(){
    ...
}
```

* 方法參數或區域變數的命名 ，參數或區域變數用小寫開頭變數名(小駝峰命名)

```kotlin
fun getFundList(token: String, pushToken: String){
    ...    
}
```

* 有Boolean回傳值時的命名，以is, can, has 或xxxable為結尾，例如：isOpened , canRead, hasValue, visiable

```kotlin
fun isFinish : Boolean{
    return false
}
```

如果方法的參數，無法用一行表達時，須將參數個別單獨為一行。

```kotlin
fun sayHello(): String {
    return "Hello"
}
```

只有Kotlin可以寫成一行

```kotlin
fun sayHello(): String = "Hello"
```

* 如果callBack方法以Lambda寫法 Lambda傳遞的參數放在第一行

```kotlin
setOnclickListener { prop ->
    val propertyValue = prop.get(obj)
}
```

#### Package name 的命名

* 全部小寫

#### Resource 的命名

全小寫 規則：以畫面使用的位置＋功能 來命名，例如以下

* activity\_xxx.xml
* fragment\_xxx.xml
* item\_xxx.xml
* layout\_xxx.xml
* dialog\_xxx.xml
* shape\_xxx.xml
* selector\_xxx.xml
* .....

例外: 模組因Gradle合併資源可能會與專案衝突，所以命名要加prefix 舉例: 社團模組之發文頁Layout

* community\_layout\_activity\_newpost

自選股模組之編輯自選股頁Layout

* customgroup\_layout\_activity\_editcustomgroup

#### View Id的命名

規則：功能＋使用元件 來命名，例如以下

* login\_textView
* reply\_us\_editText
* xxx\_textView
* xxx\_editText
* xxx\_button
* xxx\_linearLayout(constraintLayout,..)
* ...

### 修飾詞

* 可見性修飾詞
  * Kotlin 可見性修飾詞 private、 protected、 internal 和 public ，預設可見性是 public。
  * Java 可見性修飾詞 private、package-private、 protected 和 public，預設可見性是 package-private。[相關連結](https://openhome.cc/Gossip/JavaEssence/PackageAndModifier.html)
* 順序

```
public / protected / private / internal
expect / actual
final / open / abstract / sealed / const
external
override
lateinit
tailrec
vararg
suspend
inner
enum / annotation
companion
inline
infix
operator
data
```

```
public protected private 
abstract default 
static final 
transient volatile 
synchronized native strictfp
```

### 型別

對於Kotlin基礎型別有疑惑的，可以去[這邊](https://www.kotlincn.net/docs/reference/)尋找答案，這邊只寫需要注意的部分。Java的往[這邊](http://www.codedata.com.tw/book/java-basic/index.php)。

* 實質型別與參考型別的差別 [相關連結](http://tianchyi1955.pixnet.net/blog/post/101762587-4-----%E8%B3%87%E6%96%99%E7%9A%84%E5%AF%A6%E5%80%BC%E5%9E%8B%E5%88%A5%E5%8F%8A%E5%8F%83%E8%80%83%E5%9E%8B%E5%88%A5)
* 注意boxing與unboxing的成本 [相關連結](https://github.com/JustinSDK/JavaSE6Tutorial/blob/master/docs/CH04.md)

#### 轉型

* 數值向下轉型會有遺失資料的風險

#### 安全轉型 vs 不安全轉型

安全轉型方式

* kotlin

安全

```kotlin
open class Car {
}

class Taxi : Car() {
}

fun main() {
    val tempCar: Car = Car()
    val tempTaxi: Taxi? = tempCar as? Taxi
    if (tempTaxi == null) {
        return
    }
}
```

不安全

```kotlin
val tempTaxi: Taxi = Taxi()

val tempCar: Car = tempTaxi as Car // 轉型成功

if(tempCar is Car) {
    tempCar.xxx()
}
```

* java 安全

```java
class Car {
}

class Taxi extends Car {
}

Car tempCar = new Taxi();

if (tempCar instanceof Taxi)) {
  Taxi tempTaxi = (Taxi)tempCar;
  do something..
}
```

不安全

```java
class Car {
}

class Taxi extends Car {
}

Car tempCar = new Taxi();

Taxi tempTaxi = (Taxi)tempCar;
do something..
```

### 字串

* 字串的不變性請參考[這裡](https://blog.csdn.net/u012830807/article/details/17068091)
* 不要什麼東西都存字串，成本很重。
* 有需要迴圈來組字串時只能使用StringBuilder
* 除了需要在迴圈裡組字串情況外，都使用+號或StringTemplate，使用+號是因為在編譯時會把+轉成StringBuilder來實現，所以不必擔心記憶體使用問題，而且使用+能使可讀性與可維護性提升

```kotlin
// StringTemplate範例
val s = "abc"
println("$s.length is ${s.length}")
```

* 三個以上參數時不要用String.format，會造成可讀性與維護性下降

```kotlin
// don't
String.format("first:%d second:%d third:%d",a,b,c)
```

* 字串轉型態 可以直接 "String".toIntOrNull()、"String".toDoubleOrNull()....
* 比較字串可以直接用 "FirstString" == "SecondString" 的寫法 ，Kotlin有幫你做好了。如果需要忽略大小寫可以這樣寫 "FirstString".equals("SecondString",true)
* 請使用 string.IsNullOrEmpty() 來檢查 null or 空字串，而非 str == null 或 str == ""

Java 特別注意的部分：

* \[Java String Concatenation: Which Way Is Best?]<https://redfin.engineering/java-string-concatenation-which-way-is-best-8f590a7d22a8>
* 比較字串請不要用以下的寫法 ，這是比較物件是否相等

```java
if( "originalString" == "compareString" )
```

正確寫法為

```java
if( "originalString".equals("compareString") )
```

或

```java
if( "originalString".compareTo("compareString") == 0 )
```

### Nullable與NonNullable

Kotlin的型別皆可以Nullable，因此在使用上有幾項要注意的地方

* 非必要不將型別、屬性及欄位設為Nullable型態
* 非必要不使用!!(斷定NonNullable)，因為這樣就拋棄了Kotlin自動安全檢查null的優勢
  * 若是在一定要用到值的時候則可以使用!! or requireNotNull，以確保流程正確，但是如此做需要做例外處理，不可直接使APP崩潰
  * 範例: LiveData已經設定為Boolean，但是方法因為是從Java編譯，所以還是可能為Null，故可以使用!!orrequireNotNull進行取值

```kotlin
val isLoading: LiveData<Boolean> = MutableLiveData<Boolean>(false)
val state = isLoading.value!!
```

### 註解

註解可以記下開發流程和緣由和思緒流程，特殊的邏輯一定要寫註解。 註解要和程式碼一起維護，不要留下過時的註解。

* 單行註解 可用 /\* ... \*/ 或 // ... 。

```kotlin
// End-Of-Line Comments
/* Single-Line Comments */
```

* 區塊註解 註解區塊的縮排，和其接連的程式碼同一層級。可用 /\* ... */ 或 // ... 。若是這種註解風格/* ... */有多行時，其子行的起始必需有*，而該星號需對齊上一行的 \*。

```kotlin
/*
 * This is
 * okay.
 */
```

```kotlin
// And so
// is this.
```

```kotlin
/* Or you can
 * even do this. */
```

\*這邊有撰寫一個範例: 如下

```kotlin
/**
 * 取得選擇年月的月初日期
 * @param twYear  民國 X 年
 * @param month 月 (不必 -1)
 * @return 西元年月日 格式 = "yyyy/MM/dd"
 */
```

* Javadoc 置於 Class 宣告、Method 宣告、Field 宣告或 Property 宣告之前。 必須要寫 Javadoc 的有 public class 以及所有的 public 或 protected 成員 (member) 。
  * 使用/\*\* ... */註解，其子行的起始必需有*，而該星號需對齊上一行的 \*。

```java
/**  
  * Multiple lines of Javadoc text are written here, 
  * wrapped normally... 
  */
public int method(String p1) { 
    ... 
}
```

如果只有單行可以縮成

```java
/** An especially short bit of Javadoc. */
```

* 不用寫 Javadoc 的例外
  * 在子物中覆寫 (override) 父類別的方法不一定要寫上 Javadoc，若執行邏輯中有不易理解的(若不清楚可以徵詢其他人CodeReview)則需要寫上註解。
  * 有些「簡單、明確」的方法也不一定要寫上 Javadoc，像是 `getFoo` 這種簡明的案例，好像除了寫上「返回 foo 值」也什麼好寫的。但如果對於一般人來說並不能馬上知道 **Foo** 是什麼意思，那就要寫 Javadoc。
  * 方法的回傳
* TODO 善用 IDE 的 TODO 功能，對那些臨時的、短期的解決方案，或已經夠好但仍不完美的程式碼使用 TODO 或 FIXME 註解。 TODO 是總稱， FIXME 是細分。 用 FIXME 來表示存在問題或優先度較高的 TODO 事項（可選）

```kotlin
// TODO: 2019/2/14 待重構

// FIXME: 2019/2/14 有未知的狀況會Crash
```

\*這邊有示範如何快速撰寫todo與尋找todo

* 註解程式碼 請不要註解程式碼，不如直接拿掉。 若真的有需要，一律使用// ...。

### 擴充方法

* 放置位置：放在$root/extension的資料夾底下

### 例外處理

* 不要忽略例外 Don't

```kotlin
fun setServerPort(value : String) {
    try {
        serverPort = value.toInt()
    } catch (e : NumberFormatException) { 

    }
}
```

* 如果沒有要處理，就不要 catch，只在可以處理的地方 catch。
* 不要直接 catch 最上層的 Exception 或 Throwable Don't

```kotlin
catch (e1 : Exception) {
}
```

* Finally 區塊裡面只用來作資源回收或清理資料結構的工作，或使用 Try-with-resources 來自動處理資源回收。

```kotlin
resource = acquireResource()
try {
    useResource(resource)
} finally {
    releaseResource(resource)
}
```

或是

```kotlin
val writer = FileWriter("test.txt")
writer.use {
    writer.write("something")
}
```

* 不要嵌套 try catch ，會造成可讀性與維護性下降，請把需要try-catch的地方包成一個方法，並把Exception丟出 Don't

```kotlin
try {
    ...
    try {
        ... 
    } catch (e2 : WhateverException) {  
        // Exception Message  
    }  
} catch (e1 : FooException) {  
    // Exception Message  
}
```

Do

```kotlin
try {
    ...
    throwExceptionMethod()
} catch (e1 : FooException) {  
    // Exception Message  
} catch (e2 : WhateverException) {  
    // Exception Message  
}
```

### 附錄

#### 參考網站

* <https://kotlinlang.org/docs/reference/>
* <https://www.kotlincn.net/docs/reference/>
* <https://blog.csdn.net/u012830807/article/details/17068091>
