Hướng dẫn về quy tắc lập trình bằng Kotlin

Tài liệu này là định nghĩa hoàn chỉnh về các tiêu chuẩn lập trình Android của Google dành cho mã nguồn bằng Ngôn ngữ lập trình Kotlin. Tệp nguồn Kotlin được coi là đúng tiêu chuẩn lập trình Android của Google khi và chỉ khi tệp đó tuân thủ các quy tắc trong tài liệu này.

Giống như hướng dẫn về quy tắc lập trình bằng các ngôn ngữ khác, những vấn đề được đề cập ở đây không chỉ là vấn đề thẩm mỹ về hình thức, mà còn nói đến các loại quy ước hoặc tiêu chuẩn lập trình khác. Tuy nhiên, tài liệu này chủ yếu tập trung vào các quy tắc khó và ngắn gọn mà chúng tôi áp dụng trong mọi trường hợp. Chúng tôi cũng tránh đưa ra lời khuyên không thể thực thi rõ ràng (bất kể do con người hay công cụ thực hiện).

Lần cập nhật gần đây nhất: ngày 19 tháng 5 năm 2021

Tệp nguồn

Tất cả các tệp nguồn phải được mã hoá dưới dạng UTF-8.

Đặt tên

Nếu tệp nguồn chỉ chứa một lớp cấp cao nhất, thì tên tệp phải là tên phân biệt chữ hoa chữ thường và có phần mở rộng .kt. Còn nếu tệp nguồn chứa nhiều nội dung khai báo cấp cao nhất, hãy chọn tên mô tả nội dung của tệp, áp dụng PascalCase và thêm phần mở rộng .kt.

// MyClass.kt
class MyClass { }
// Bar.kt
class Bar { }
fun Runnable.toBar(): Bar = // …
// Map.kt
fun <T, O> Set<T>.map(func: (T) -> O): List<O> = // …
fun <T, O> List<T>.map(func: (T) -> O): List<O> = // …

Ký tự đặc biệt

Ký tự khoảng trắng

Ngoài dãy ngắt dòng, ký tự khoảng trắng ngang ASCII (0x20) là ký tự khoảng trắng duy nhất xuất hiện ở vị trí bất kỳ trong tệp nguồn. Điều này có nghĩa là:

  • Tất cả các ký tự khoảng trắng khác trong chuỗi và giá trị cố định dạng ký tự đều được thoát.
  • Ký tự tab không được dùng để thụt lề.

Dãy ký tự thoát đặc biệt

Đối với những ký tự có dãy ký tự thoát đặc biệt (\b, \n, \r, \t, \', \", \\\$), dãy đó được sử dụng thay cho ký tự thoát tương ứng của Unicode (ví dụ: \u000a).

Ký tự không phải ASCII

Đối với các ký tự còn lại không phải ASCII, hãy dùng ký tự Unicode (ví dụ: ) hoặc ký tự thoát tương đương của Unicode (ví dụ: \u221e). Bạn chỉ cần chọn cách nào giúp mã dễ đọc và dễ hiểu hơn. Ở bất kỳ vị trí nào, bạn cũng không nên sử dụng ký tự thoát Unicode cho các ký tự in được, đặc biệt là không nên sử dụng bên ngoài giá trị cố định dạng chuỗi và ghi chú.

Ví dụ Thảo luận
val unitAbbrev = "μs" Tốt nhất: hoàn toàn rõ ràng ngay cả khi không có ghi chú.
val unitAbbrev = "\u03bcs" // μs Kém: không có lý do gì để sử dụng ký tự thoát cho ký tự in được.
val unitAbbrev = "\u03bcs" Kém: người đọc không biết đây là gì.
return "\ufeff" + content Tốt: sử dụng ký tự thoát cho ký tự không in được và ghi chú nếu cần.

Cấu trúc

Tệp .kt bao gồm các mục sau theo thứ tự:

  • Tiêu đề bản quyền và/hoặc tiêu đề giấy phép (không bắt buộc)
  • Chú giải cấp tệp
  • Câu lệnh cho gói
  • Câu lệnh nhập
  • Nội dung khai báo cấp cao nhất

Duy nhất một dòng trống, phân tách từng phần như trên với nhau.

Nếu tiêu đề bản quyền hoặc tiêu đề giấy phép nằm trong tệp đó, bạn nên đặt tiêu đề này ngay ở trên cùng bằng ghi chú nhiều dòng.

/*
 * Copyright 2017 Google, Inc.
 *
 * ...
 */
 

Đừng sử dụng ghi chú kiểu KDoc hoặc kiểu một dòng.

/**
 * Copyright 2017 Google, Inc.
 *
 * ...
 */
// Copyright 2017 Google, Inc.
//
// ...

Chú giải cấp tệp

Chú giải có mục tiêu toàn tệp là "file" được đặt giữa mục khai báo cho gói và ghi chú tiêu đề.

Câu lệnh cho gói

Câu lệnh cho gói không phải tuân theo giới hạn cột và không bao giờ tự xuống dòng.

Câu lệnh nhập

Các câu lệnh nhập cho lớp, hàm và thuộc tính được nhóm lại với nhau trong một danh sách và được sắp xếp thứ tự theo ASCII.

Chúng tôi không cho phép nhập ký tự đại diện (thuộc bất kỳ loại nào).

Tương tự như câu lệnh cho gói, các câu lệnh nhập không phải tuân theo giới hạn cột và không bao giờ tự xuống dòng.

Nội dung khai báo cấp cao nhất

Tệp .kt có thể khai báo một hoặc nhiều loại, hàm, thuộc tính hoặc tên đại diện cho loại ở cấp cao nhất.

Nội dung của tệp phải tập trung vào một chủ đề duy nhất. Ví dụ: một loại công khai hoặc một nhóm hàm mở rộng thực hiện cùng một thao tác trên nhiều loại trình nhận. Bạn nên tách riêng các nội dung khai báo không liên quan thành tệp riêng và hạn chế đưa các nội dung khai báo công khai vào cùng một tệp.

Không có hạn chế rõ ràng nào về số lượng và thứ tự của nội dung tệp.

Tệp nguồn thường được đọc từ trên xuống, nghĩa là thứ tự nội dung thường phải cho thấy rằng các nội dung khai báo nằm ở trên sẽ cung cấp thông tin cho các nội dung khai báo đó ở dưới. Các tệp khác nhau có thể chọn sắp xếp nội dung theo cách khác nhau. Tương tự, một tệp có thể chứa 100 thuộc tính, 10 hàm và 1 lớp.

Điều quan trọng là mỗi tệp phải sử dụng một thứ tự logic mà người duy trì tệp có thể giải thích nếu cần. Ví dụ: các hàm mới không được thêm ngẫu nhiên vào cuối tệp vì làm như vậy sẽ tạo ra thứ tự "thời gian theo ngày thêm" chứ không phải là thứ tự logic.

Thứ tự thành phần của lớp

Thứ tự của các thành phần trong một lớp tuân theo quy tắc giống như nội dung khai báo cấp cao nhất.

Định dạng

Dấu ngoặc nhọn

Không bắt buộc phải có dấu ngoặc nhọn {} cho nhánh của câu lệnh when cũng như phần nội dung của những câu lệnh if chỉ có nhiều nhất một else và nằm vừa trên một dòng.

if (string.isEmpty()) return

val result =
    if (string.isEmpty()) DEFAULT_VALUE else string

when (value) {
    0 -> return
    // …
}

Ngoài ra, dấu ngoặc nhọn là bắt buộc đối với nhánh của câu lệnh if, for, when, dowhile, ngay cả khi phần body (nội dung) trống hoặc chỉ chứa một một câu lệnh.

if (string.isEmpty())
    return  // WRONG!

if (string.isEmpty()) {
    return  // Okay
}

Khối không trống

Dấu ngoặc nhọn phải tuân theo kiểu Kernighan & Ritchie ("dấu ngoặc Ai Cập") đối với các khối không trống và các cấu trúc giống khối:

  • Không ngắt dòng trước dấu mở ngoặc nhọn.
  • Ngắt dòng sau dấu mở ngoặc nhọn.
  • Ngắt dòng trước dấu đóng ngoặc nhọn.
  • Ngắt dòng sau dấu đóng ngoặc nhọn, chỉ khi dấu ngoặc nhọn kết thúc câu lệnh hoặc kết thúc phần body của hàm, hàm dựng hoặc lớp có tên. Ví dụ: không ngắt dòng sau dấu ngoặc nhọn nếu dấu ngoặc nhọn đứng trước else hoặc dấu phẩy.
return Runnable {
    while (condition()) {
        foo()
    }
}

return object : MyClass() {
    override fun foo() {
        if (condition()) {
            try {
                something()
            } catch (e: ProblemException) {
                recover()
            }
        } else if (otherCondition()) {
            somethingElse()
        } else {
            lastThing()
        }
    }
}

Dưới đây là một số trường hợp ngoại lệ về lớp loại enum.

Khối trống

Khối trống hoặc cấu trúc giống khối phải theo kiểu K&R.

try {
    doSomething()
} catch (e: Exception) {} // WRONG!
try {
    doSomething()
} catch (e: Exception) {
} // Okay

Cụm từ

Một điều kiện if/else được dùng làm biểu thức có thể bỏ dấu ngoặc nhọn chỉ khi toàn bộ biểu thức nằm vừa trên một dòng.

val value = if (string.isEmpty()) 0 else 1  // Okay
val value = if (string.isEmpty())  // WRONG!
    0
else
    1
val value = if (string.isEmpty()) { // Okay
    0
} else {
    1
}

Thụt lề

Mỗi khi một khối mới hoặc một cấu trúc giống được mở, phần thụt lề sẽ tăng thêm bốn dấu cách. Khi khối kết thúc, mức thụt lề sẽ trở lại như trước. Mức thụt lề áp dụng cho cả mã và ghi chú trong toàn bộ khối.

Một câu lệnh trên mỗi dòng

Sau mỗi câu lệnh sẽ có một dấu ngắt dòng. Không dùng dấu chấm phẩy.

Tự xuống dòng

Mã có giới hạn cột là 100 ký tự. Ngoại trừ những trường hợp như bên dưới, tất cả các dòng vượt quá giới hạn này đều phải tự xuống dòng như phần giải thích bên dưới.

Ngoại lệ:

  • Những dòng không thể tuân thủ giới hạn cột này (ví dụ: một URL dài trong KDoc)
  • Câu lệnh packageimport
  • Các dòng lệnh trong một ghi chú có thể được cắt và dán vào một ô

Vị trí ngắt

Mục tiêu chính của việc xuống dòng là: ưu tiên xuống dòng ở cấp cú pháp cao hơn. Ngoài ra:

  • Khi một dòng bị ngắt ở tên toán tử hoặc tên hàm infix, thì dấu ngắt sẽ ở sau tên toán tử hoặc hàm infix.
  • Khi một dòng bị ngắt ở các ký hiệu "giống toán tử" như sau, dấu ngắt sẽ ở trước ký hiệu đó:
    • Dấu chấm phân cách (., ?.).
    • Hai dấu hai chấm của một giá trị tham chiếu con (::).
  • Phương thức hoặc tên hàm dựng luôn được đính kèm với dấu ngoặc đơn mở (() theo sau.
  • Dấu phẩy (,) luôn được đính kèm vào đoạn mã đứng trước.
  • Mũi tên lambda (->) luôn đính kèm vào danh sách đối số đứng trước.

Hàm

Khi chữ ký hàm không nằm vừa trên một dòng, hãy ngắt từng khai báo thông số thành một dòng riêng. Các thông số được xác định ở định dạng này nên sử dụng dấu thụt lề một lần duy nhất (+4). Dấu đóng ngoặc đơn ()) và loại dữ liệu trả về được đặt trên một dòng riêng mà không thụt lề thêm.

fun <T> Iterable<T>.joinToString(
    separator: CharSequence = ", ",
    prefix: CharSequence = "",
    postfix: CharSequence = ""
): String {
    // …
}
Hàm biểu thức

Khi một hàm chỉ chứa một biểu thức duy nhất, thì hàm đó có thể được biểu thị dưới dạng một hàm biểu thức.

override fun toString(): String {
    return "Hey"
}
override fun toString(): String = "Hey"

Thuộc tính

Khi trình khởi chạy thuộc tính không nằm vừa trên một dòng, hãy ngắt dòng sau dấu bằng (=) và sử dụng dấu thụt lề.

private val defaultCharset: Charset? =
    EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)

Các thuộc tính khai báo hàm get và/hoặc set phải đặt mỗi hàm trên một dòng riêng với mức thụt lề thông thường (+4). Định dạng bằng cách sử dụng cùng các quy tắc áp dụng cho hàm.

var directory: File? = null
    set(value) {
        // …
    }
Thuộc tính chỉ đọc có thể sử dụng cú pháp ngắn hơn và nằm vừa trên một dòng.
val defaultExtension: String get() = "kt"

Khoảng trắng

Dọc

Một dòng trống duy nhất xuất hiện:

  • Giữa các thành phần liên tiếp của một lớp: thuộc tính, hàm dựng, hàm, lớp được lồng, v.v.
    • Ngoại lệ: Bạn không bắt buộc phải sử dụng dòng trống giữa hai thuộc tính liên tiếp (không có mã khác giữa các thuộc tính này). Các dòng trống như vậy được dùng khi cần thiết để tạo nhóm thuộc tính theo logic và liên kết các thuộc tính với thuộc tính dự phòng tương ứng (nếu có).
    • Ngoại lệ: Dòng trống giữa các hằng enum được đề cập trong phần bên dưới.
  • Giữa các câu lệnh, khi cần để sắp xếp mã thành các tiểu mục hợp lý.
  • (Không bắt buộc) trước câu lệnh đầu tiên trong một hàm, trước thành phần đầu tiên của một lớp hoặc sau thành phần cuối cùng của một lớp (không có quy định về việc nên hay không nên áp dụng cách làm này).
  • Theo yêu cầu của các phần khác trong tài liệu này (chẳng hạn như phần Cấu trúc).

Chúng tôi chấp nhận nhiều dòng trống liên tiếp, nhưng không khuyến nghị và không yêu cầu phải áp dụng cách làm này.

Ngang

Ngoài vị trí yêu cầu theo ngôn ngữ hoặc các quy tắc khác về hình thức, cũng như ngoài giá trị cố định, ghi chú và KDoc, thì một khoảng trắng ASCII cũng chỉ xuất hiện ở những vị trí sau:

  • Tách từ dành riêng, chẳng hạn như if, for hoặc catch khỏi dấu mở ngoặc đơn (() theo sau trên dòng đó.
    // WRONG!
    for(i in 0..1) {
    }
    
    // Okay
    for (i in 0..1) {
    }
    
  • Tách từ dành riêng, chẳng hạn như else hoặc catch, khỏi dấu đóng ngoặc nhọn (}) đứng trước trên dòng đó.
    // WRONG!
    }else {
    }
    
    // Okay
    } else {
    }
    
  • Trước dấu mở ngoặc nhọn ({).
    // WRONG!
    if (list.isEmpty()){
    }
    
    // Okay
    if (list.isEmpty()) {
    }
    
  • Ở cả hai bên của các toán tử nhị phân.
    // WRONG!
    val two = 1+1
    
    // Okay
    val two = 1 + 1
    
    Nguyên tắc này cũng áp dụng cho các ký hiệu giống toán tử sau đây:
    • mũi tên trong biểu thức lambda (->).
      // WRONG!
      ints.map { value->value.toString() }
      
      // Okay
      ints.map { value -> value.toString() }
      
    Nhưng không áp dụng cho:
    • hai dấu hai chấm (::) của một giá trị tham chiếu con.
      // WRONG!
      val toString = Any :: toString
      
      // Okay
      val toString = Any::toString
      
    • dấu chấm phân cách (.).
      // WRONG
      it . toString()
      
      // Okay
      it.toString()
      
    • toán tử phạm vi (..).
      // WRONG
       for (i in 1 .. 4) print(i)
       
       // Okay
       for (i in 1..4) print(i)
      
  • Trước dấu hai chấm (:), chỉ khi được dùng trong phần khai báo lớp để chỉ định giao diện hay lớp cơ sở hoặc khi dùng trong mệnh đề where cho các loại ràng buộc chung.
    // WRONG!
    class Foo: Runnable
    
    // Okay
    class Foo : Runnable
    
    // WRONG
    fun <T: Comparable> max(a: T, b: T)
    
    // Okay
    fun <T : Comparable> max(a: T, b: T)
    
    // WRONG
    fun <T> max(a: T, b: T) where T: Comparable<T>
    
    // Okay
    fun <T> max(a: T, b: T) where T : Comparable<T>
    
  • Sau dấu phẩy (,) hoặc dấu hai chấm (:).
    // WRONG!
    val oneAndTwo = listOf(1,2)
    
    // Okay
    val oneAndTwo = listOf(1, 2)
    
    // WRONG!
    class Foo :Runnable
    
    // Okay
    class Foo : Runnable
    
  • Ở cả hai bên của dấu gạch chéo đôi (//) bắt đầu một ghi chú cuối dòng. Ở đây, bạn được phép sử dụng nhiều dấu cách nhưng không bắt buộc.
    // WRONG!
    var debugging = false//disabled by default
    
    // Okay
    var debugging = false // disabled by default
    

Quy tắc này không có nghĩa là chúng tôi yêu cầu hoặc cấm việc có thêm khoảng trống ở đầu hoặc cuối dòng; quy tắc chỉ áp dụng cho khoảng trống bên trong.

Cấu trúc cụ thể

Lớp loại enum

Bạn có thể định dạng một enum không có hàm và không có tài liệu về hằng thành một dòng duy nhất.

enum class Answer { YES, NO, MAYBE }

Khi đặt các hằng của một enum trên các dòng riêng biệt thì không cần phải có dòng trống, ngoại trừ trường hợp các hằng này xác định một body.

enum class Answer {
    YES,
    NO,

    MAYBE {
        override fun toString() = """¯\_(ツ)_/¯"""
    }
}

Vì lớp loại enum vẫn là lớp nên tất cả quy tắc khác để định lớp sẽ được áp dụng.

Chú giải

Chú giải về thành phần hoặc loại được đặt trên các dòng riêng biệt ngay trước cấu trúc được chú giải.

@Retention(SOURCE)
@Target(FUNCTION, PROPERTY_SETTER, FIELD)
annotation class Global

Chú giải không có đối số có thể được đặt trên một dòng.

@JvmField @Volatile
var disposable: Disposable? = null

Khi chỉ có một chú giải không có đối số, bạn có thể đặt chú giải đó trên cùng một dòng với phần khai báo.

@Volatile var disposable: Disposable? = null

@Test fun selectAll() {
    // …
}

Chỉ có thể sử dụng cú pháp @[...] có mục tiêu rõ ràng cho toàn tệp và chỉ để kết hợp ít nhất 2 chú giải không có đối số trên một dòng.

@field:[JvmStatic Volatile]
var disposable: Disposable? = null

Các loại thuộc tính/dữ liệu trả về không rõ ràng

Nếu phần body của hàm biểu thức hoặc trình khởi chạy thuộc tính là một giá trị vô hướng hoặc loại dữ liệu trả về có thể được suy ra rõ ràng từ body, thì body đó có thể bị bỏ qua.

override fun toString(): String = "Hey"
// becomes
override fun toString() = "Hey"
private val ICON: Icon = IconLoader.getIcon("/icons/kotlin.png")
// becomes
private val ICON = IconLoader.getIcon("/icons/kotlin.png")

Khi viết thư viện, hãy giữ lại nội dung khai báo loại rõ ràng khi đây là một phần của API công khai.

Đặt tên

Giá trị nhận dạng chỉ được sử dụng chữ số và chữ cái ASCII. Trong một số ít trường hợp, giá trị nhận dạng được sử dụng dấu gạch dưới, như phần trình bày bên dưới. Do đó, tên của mỗi giá trị nhận dạng hợp lệ sẽ được khớp với biểu thức chính quy \w+.

Không dùng các tiền tố hoặc hậu tố đặc biệt (như trong các ví dụ sau: name_, mName, s_namekName), ngoại trừ trường hợp thuộc tính dự phòng (xem Thuộc tính dự phòng).

Tên gói

Tên gói được viết hoàn toàn bằng chữ thường, có các từ liên tiếp nối liền nhau (không có dấu gạch dưới).

// Okay
package com.example.deepspace
// WRONG!
package com.example.deepSpace
// WRONG!
package com.example.deep_space

Nhập tên

Tên lớp được viết theo quy ước PascalCase và thường là danh từ hoặc cụm danh từ. Ví dụ: Character hoặc ImmutableList. Tên giao diện cũng có thể là danh từ hoặc cụm danh từ (ví dụ: List), nhưng đôi khi có thể là tính từ hoặc cụm tính từ (ví dụ: Readable).

Các lớp kiểm thử có tên bắt đầu bằng tên của lớp đang được kiểm thử và kết thúc bằng Test. Ví dụ: HashTest hoặc HashIntegrationTest.

Tên hàm

Tên hàm được viết theo quy ước camelCase và thường là động từ hoặc cụm động từ. Ví dụ: sendMessage hoặc stop.

Dấu gạch dưới được phép xuất hiện trong tên hàm kiểm thử để phân tách các thành phần logic của tên.

@Test fun pop_emptyStack() {
    // …
}

Các hàm được chú giải bằng @Composable trả về Unit tuân theo quy ước PascalCase và được đặt tên bằng danh từ, giống như loại.

@Composable
fun NameTag(name: String) {
    // …
}

Tên hàm không được chứa dấu cách vì điều này không được hỗ trợ trên mọi nền tảng (xin lưu ý rằng tên có dấu cách chưa được hỗ trợ đầy đủ trong Android).

// WRONG!
fun `test every possible case`() {}
// OK
fun testEveryPossibleCase() {}

Tên hằng

Tên hằng sử dụng quy ước UPPER_SNAKE_CASE: toàn bộ là chữ cái viết hoa, các từ được phân cách bằng dấu gạch dưới. Nhưng chính xác thì hằng là gì?

Hằng là các thuộc tính val không có hàm get tuỳ chỉnh, và nội dung của hằng không thể thay đổi sâu và hàm của hằng không có tác dụng phụ có thể phát hiện được. Trong đó bao gồm các loại không thay đổi được và tập hợp không thay đổi được của các loại không thay đổi được, cũng như các giá trị vô hướng và chuỗi khi được đánh dấu là const. Nếu một trong các trạng thái theo dõi được của một thực thể có thể thay đổi, thì đó không phải là hằng. Dù bạn không có ý định thay đổi đối tượng đó thì cũng không đủ điều kiện.

const val NUMBER = 5
val NAMES = listOf("Alice", "Bob")
val AGES = mapOf("Alice" to 35, "Bob" to 32)
val COMMA_JOINER = Joiner.on(',') // Joiner is immutable
val EMPTY_ARRAY = arrayOf()

Các tên này thường là danh từ hoặc cụm danh từ.

Bạn chỉ có thể xác định giá trị hằng trong một object hoặc trong một nội dung khai báo cấp cao nhất. Các giá trị đáp ứng yêu cầu để là một hằng nhưng được xác định bên trong class thì phải sử dụng tên không cố định.

Các hằng là giá trị vô hướng phải sử dụng biến bổ trợ const.

Tên không cố định

Tên không cố định được viết theo quy ước camelCase. Quy tắc này áp dụng cho các thuộc tính của thực thể, thuộc tính cục bộ và tên thông số.

val variable = "var"
val nonConstScalar = "non-const"
val mutableCollection: MutableSet = HashSet()
val mutableElements = listOf(mutableInstance)
val mutableValues = mapOf("Alice" to mutableInstance, "Bob" to mutableInstance2)
val logger = Logger.getLogger(MyClass::class.java.name)
val nonEmptyArray = arrayOf("these", "can", "change")

Các tên này thường là danh từ hoặc cụm danh từ.

Thuộc tính dự phòng

Khi cần một thuộc tính dự phòng, tên của thuộc tính đó phải khớp chính xác với thuộc tính thực ngoại trừ dấu gạch dưới.

private var _table: Map? = null

val table: Map
    get() {
        if (_table == null) {
            _table = HashMap()
        }
        return _table ?: throw AssertionError()
    }

Tên của biến loại

Mỗi biến loại được đặt tên theo một trong hai kiểu:

  • Một chữ cái viết hoa, theo sau có thể là một chữ số nhưng không bắt buộc (chẳng hạn như E, T, X, T2)
  • Tên trong biểu mẫu dùng cho các lớp, theo sau là chữ cái viết hoa T (chẳng hạn như RequestT, FooBarT)

Quy tắc viết hoa Camel

Đôi khi có nhiều cách hợp lý để chuyển đổi một cụm từ tiếng Anh thành kiểu viết hoa camel, chẳng hạn như khi có từ viết tắt hoặc cấu trúc khác thường như "IPv6" hoặc "iOS". Để cải thiện khả năng dự đoán, hãy sử dụng lược đồ sau.

Bắt đầu bằng việc tạo tên theo ngôn ngữ tự nhiên:

  1. Chuyển đổi cụm từ đó sang ký tự ASCII thuần tuý và xoá mọi dấu nháy đơn. Ví dụ: “Müller’s algorithm” có thể trở thành “Muellers algorithm”.
  2. Chia kết quả này thành các từ, mỗi từ phân tách với nhau bằng dấu cách và các dấu câu còn lại nếu có (thường là dấu gạch nối). Đề xuất: nếu có từ đã được viết theo kiểu camel trong ngữ cảnh bình thường, hãy tách riêng các phần tạo nên từ đó (ví dụ: "AdWords" trở thành "ad words"). Xin lưu ý rằng một từ như “iOS” không thực sự là kiểu camel và cũng không thuộc quy ước nào nên không áp dụng đề xuất này.
  3. Bây giờ, hãy viết thường tất cả các từ (kể cả từ viết tắt) rồi thực hiện một trong những thao tác sau:
    • Viết hoa chữ cái đầu tiên của mỗi từ để thành kiểu viết hoa pascal.
    • Viết hoa chữ cái đầu tiên của mỗi từ, ngoại trừ chữ cái đầu tiên của cả cụm từ để thành kiểu viết hoa camel.
  4. Cuối cùng, đưa tất cả các từ vào một giá trị nhận dạng duy nhất.

Lưu ý rằng cách viết hoa của từ gốc gần như không còn được giữ lại.

Tên theo ngôn ngữ tự nhiên Đúng Sai
"XML Http Request" XmlHttpRequest XMLHTTPRequest
"new customer ID" newCustomerId newCustomerID
"inner stopwatch" innerStopwatch innerStopWatch
"supports IPv6 on iOS" supportsIpv6OnIos supportsIPv6OnIOS
"YouTube importer" YouTubeImporter YoutubeImporter*

(* Được chấp nhận, nhưng không nên áp dụng.)

Tài liệu

Định dạng

Dưới đây là định dạng cơ bản của khối KDoc:

/**
 * Multiple lines of KDoc text are written here,
 * wrapped normally…
 */
fun method(arg: String) {
    // …
}

...hoặc trong ví dụ về dòng đơn này:

/** An especially short bit of KDoc. */

Hình thức cơ bản luôn được chấp nhận. Bạn có thể thay thế dạng một dòng khi toàn bộ khối KDoc (bao gồm cả các mã đánh dấu ghi chú) có thể nằm vừa trên một dòng. Lưu ý rằng điều này chỉ áp dụng khi không có các thẻ dạng khối như @return.

Đoạn

Một dòng trống – tức là dòng chỉ chứa dấu hoa thị ở đầu hàng được căn chỉnh (*)—xuất hiện giữa các đoạn với nhau và ở trước nhóm thẻ dạng khối (nếu có).

Thẻ dạng khối

Bất kỳ "thẻ dạng khối" chuẩn nào đang được sử dụng sẽ xuất hiện theo thứ tự @constructor, @receiver, @param, @property, @return, @throws , @see và các thẻ này không bao giờ xuất hiện cùng với phần mô tả trống. Khi thẻ dạng khối không vừa trên một dòng, các dòng tiếp theo sẽ thụt vào một khoảng bằng 4 dấu cách, tính từ vị trí của @.

Mảnh tóm tắt

Mỗi khối KDoc bắt đầu bằng một mảnh tóm tắt ngắn gọn. Mảnh này rất quan trọng: đó là phần duy nhất của văn bản xuất hiện trong một số ngữ cảnh nhất định như lớp và chỉ mục các phương thức.

Đây là một mảnh – một cụm danh từ hoặc cụm động từ, không phải là một câu hoàn chỉnh. Mảnh này không bắt đầu bằng "A `Foo` is a...", hay "This method returns..." và không cần phải tạo thành một câu hoàn chỉnh như "Save the record.". Tuy nhiên, mảnh được viết hoa và đặt dấu câu như thể đó là một câu hoàn chỉnh.

Mức sử dụng

Ở mức tối thiểu, KDoc luôn có mặt cho mọi loại public, và mọi thành phần public hoặc protected của loại đó, ngoại trừ một vài trường hợp ngoại lệ được lưu ý ở bên dưới.

Trường hợp ngoại lệ: Hàm dễ hiểu

Bạn không bắt buộc phải dùng KDoc đối với các hàm “đơn giản, rõ ràng” như getFoo và các thuộc tính như foo, trong trường hợp bạn thực sự không có gì để nói ngoài "Returns foo" (trả về foo).

Bạn không được lấy trường hợp ngoại lệ này làm lý do để bỏ qua thông tin liên quan mà độc giả thông thường cần phải biết. Ví dụ: đối với hàm có tên getCanonicalName hoặc thuộc tính có tên canonicalName, đừng bỏ qua việc ghi chép lại hàm đó (với lý do là nội dung chỉ bao gồm /** Returns the canonical name. */) nếu người đọc thông thường có thể không biết từ "canonical name" (tên chính tắc) nghĩa là gì!

Ngoại lệ: ghi đè

KDoc không phải lúc nào cũng hiện diện trên một phương thức có khả năng ghi đè phương thức siêu loại dữ liệu.