Вы здесь:

Конспект Kotlin Code Style: Best Practices for Former Java Developers

Частые ошибки:

  • Использование if (x != null) вместо оператора Elvis ?
  • Цикл и временная коллекция для результатов, вместо filter, map
  • if/else if вместо when
  • Частое использование var, где можно использовать val

Много одиночных файлов для Data Class вместо одного

Java-style (до):

// UserRequest.kt
data class UserRequest(val name: String, val email: String)

// UserResponse.kt
data class UserResponse(val id: Long, val name: String)

// UserError.kt
data class UserError(val code: String, val message: String)

Kotlin-style (после):

// UserModels.kt
data class UserRequest(val name: String, val email: String)
data class UserResponse(val id: Long, val name: String)
data class UserError(val code: String, val message: String)

 

Константы в Companion Object нужно размещать в конце файла

Java-style (до):

public class Config {
    public static final String API_ENDPOINT = "https://api.example.com";
}

Kotlin-style (после):

class NegativeFeedbackDialogFragment {
    /*.. business logic ..*/

    private companion object : KLogging() {
        const val API_ENDPOINT = "https://api.example.com"
    }
}

 

Для однострочных выражений используется fun

Java-style (до):

fun calculateDiscount(price: Double, discountPercent: Int): Double {
  return price * (1 - discountPercent / 100.0)
}

fun isAdult(age: Int): Boolean {
  return age >= 18
}

fun getUserFullName(user: User): String {
  return "${user.firstName} ${user.lastName}"
}

Kotlin-style (после):

fun calculateDiscount(price: Double, discountPercent: Int): Double =
    price * (1 - discountPercent / 100.0)

fun isAdult(age: Int): Boolean = age >= 18

fun getUserFullName(user: User): String =
    "${user.firstName} ${user.lastName}"

 

Игнорирование Elvis оператора

Java-style (до):

fun getUsername(user: User?): String {
    if (user != null) {
        if (user.name != null) {
            return user.name
        } else {
            return "Guest"
        }
    } else {
        return "Guest"
    }
}

fun calculateTotal(price: Double?): Double {
    val finalPrice: Double
    if (price != null) {
        finalPrice = price
    } else {
        finalPrice = 0.0
    }
    return finalPrice
}

Kotlin-style (после):

fun getUsername(user: User?): String =
    user?.name ?: "Guest"

fun calculateTotal(price: Double?): Double =
    price ?: 0.0

 

Игнорирование it в lambda

Java-style (до):

users.filter { user -> user.isActive }
    .map { user -> user.email }
    .forEach { email -> println(email) }

val names = users
    .filter { user -> user.age >= 18 }
    .map { user -> user.name }

Kotlin-style (после):

users.filter { it.isActive }
    .map { it.email }
    .forEach { println(it) }

val names = users
    .filter { it.age >= 18 }
    .map { it.name }

 

Игнорирование when вместо if/else

Java-style (до):

fun getStatusMessage(status: OrderStatus): String {
    if (status == OrderStatus.PENDING) {
        return "Order is being processed"
    } else if (status == OrderStatus.SHIPPED) {
        return "Order has been shipped"
    } else if (status == OrderStatus.DELIVERED) {
        return "Order delivered"
    } else if (status == OrderStatus.CANCELLED) {
        return "Order cancelled"
    } else {
        return "Unknown status"
    }
}

fun calculateFee(userType: String): Double {
    if (userType == "premium") {
        return 0.0
    } else if (userType == "standard") {
        return 5.0
    } else {
        return 10.0
    }
}

Kotlin-style (после):

fun getStatusMessage(status: OrderStatus): String = when (status) {
    OrderStatus.PENDING -> "Order is being processed"
    OrderStatus.SHIPPED -> "Order has been shipped"
    OrderStatus.DELIVERED -> "Order delivered"
    OrderStatus.CANCELLED -> "Order cancelled"
    else -> "Unknown status"
}

fun calculateFee(userType: String): Double = when (userType) {
    "premium" -> 0.0
    "standard" -> 5.0
    else -> 10.0
}

 

В коллекциях используется contains вместо in

Java-style (до):

fun isValidStatus(status: String): Boolean {
    val validStatuses = listOf("active", "pending", "completed")
    return validStatuses.contains(status)
}

fun hasPermission(role: String): Boolean {
    val adminRoles = setOf("admin", "superadmin", "moderator")
    return adminRoles.contains(role)
}

if (allowedIds.contains(userId)) {
    // logic
}

Kotlin-style (после):

fun isValidStatus(status: String): Boolean {
    val validStatuses = listOf("active", "pending", "completed")
    return status in validStatuses
}

fun hasPermission(role: String): Boolean {
    val adminRoles = setOf("admin", "superadmin", "moderator")
    return role in adminRoles
}

if (userId in allowedIds) {
    // logic
}

 

Игнорирование Stream API Kotlin

Java-style (до):

val activeUsers = users.stream()
    .filter { it.isActive }
    .collect(Collectors.toList())

val userEmails = users.stream()
    .map { it.email }
    .collect(Collectors.toSet())

val totalAmount = orders.stream()
    .mapToDouble { it.amount }
    .sum()

val hasAdult = users.stream()
    .anyMatch { it.age >= 18 }

Kotlin-style (после):

val activeUsers = users.filter { it.isActive }
val userEmails = users.map { it.email }.toSet()
val totalAmount = orders.sumOf { it.amount }
val hasAdult = users.any { it.age >= 18 }

 

let, run, with для Functional Style

Java-style (до):

fun processUser(user: User?) {
    if (user != null) {
        validateUser(user)
        saveUser(user)
        notifyUser(user)
    }
}

fun createReport(data: ReportData?): Report? {
    if (data != null) {
        val report = Report()
        report.title = data.title
        report.content = data.content
        report.timestamp = System.currentTimeMillis()
        return report
    }
    return null
}

val config = Config()
config.host = "localhost"
config.port = 8080
config.timeout = 30

Kotlin-style (после):

fun processUser(user: User?) {
    user?.let {
        validateUser(it)
        saveUser(it)
        notifyUser(it)
    }
}

fun createReport(data: ReportData?): Report? =
    data?.let {
        Report().apply {
            title = it.title
            content = it.content
            timestamp = System.currentTimeMillis()
        }
    }

val config = Config().apply {
    host = "localhost"
    port = 8080
    timeout = 30
}

 

Игнорирование Immutable коллекций

Java-style (до):

fun getActiveUsers(users: List): MutableList {
    val result = mutableListOf()
    for (user in users) {
        if (user.isActive) {
            result.add(user)
        }
    }
    return result
}

class UserRepository {
    private val users: MutableList = mutableListOf()
    fun getUsers(): MutableList = users
}

val numbers = mutableListOf(1, 2, 3, 4, 5)
val names = mutableSetOf("Alice", "Bob", "Charlie")

Kotlin-style (после):

fun getActiveUsers(users: List): List =
    users.filter { it.isActive }

class UserRepository {
    private val _users: MutableList = mutableListOf()
    val users: List get() = _users.toList()
}

val numbers = listOf(1, 2, 3, 4, 5)
val names = setOf("Alice", "Bob", "Charlie")

 

Нужно использовать val вместо var

Java-style (до):

fun calculateTotal(items: List): Double {
    var total = 0.0
    for (item in items) {
        total += item.price
    }
    return total
}

fun findUser(id: Long): User? {
    var result: User? = null
    for (user in users) {
        if (user.id == id) {
            result = user
            break
        }
    }
    return result
}

var message = "Hello"
message = message + " World"

Kotlin-style (после):

fun calculateTotal(items: List): Double =
    items.sumOf { it.price }

fun findUser(id: Long): User? =
    users.find { it.id == id }

val message = "Hello" + " World"
// or
val message = buildString {
    append("Hello")
    append(" World")
}

 

Smart Casts

Java-style (до):

fun processValue(value: Any) {
    if (value is String) {
        val str = value as String
        println(str.uppercase())
    }
}

fun getLength(obj: Any): Int {
    if (obj is String) {
        return (obj as String).length
    } else if (obj is List<*>) {
        return (obj as List<*>).size
    }
    return 0
}

when (val data = getData()) {
    is Success -> {
        val result = data as Success
        processSuccess(result.value)
    }
    is Error -> {
        val error = data as Error
        handleError(error.message)
    }
}

Kotlin-style (после):

fun processValue(value: Any) {
    if (value is String) {
        println(value.uppercase()) // automatic cast
    }
}

fun getLength(obj: Any): Int = when (obj) {
    is String -> obj.length
    is List<*> -> obj.size
    else -> 0
}

when (val data = getData()) {
    is Success -> processSuccess(data.value)
    is Error -> handleError(data.message)
}

 

Игнорирование Extension Functions

Java-style (до):

object StringUtils {
    fun isValidEmail(email: String): Boolean {
        return email.contains("@") && email.contains(".")
    }

    fun truncate(text: String, maxLength: Int): String {
        return if (text.length > maxLength) {
            text.substring(0, maxLength) + "..."
        } else {
            text
        }
    }
}

object DateUtils {
    fun formatDate(date: Date): String {
        val formatter = SimpleDateFormat("yyyy-MM-dd")
        return formatter.format(date)
    }
}

// Usage
val email = "Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript."
if (StringUtils.isValidEmail(email)) {
    // ...
}
val short = StringUtils.truncate(longText, 100)

Kotlin-style (после):

fun String.isValidEmail(): Boolean =
    contains("@") && contains(".")

fun String.truncate(maxLength: Int): String =
    if (length > maxLength) {
        substring(0, maxLength) + "..."
    } else {
        this
    }

fun Date.formatAsDate(): String =
    SimpleDateFormat("yyyy-MM-dd").format(this)

// Usage
val email = "Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript."
if (email.isValidEmail()) {
    // ...
}
val short = longText.truncate(100)

 

Игнорирование try-with-resources

Java-style (до):

fun readFile(path: String): String {
    val reader = BufferedReader(FileReader(path))
    try {
        return reader.readText()
    } finally {
        reader.close()
    }
}

fun writeToFile(path: String, content: String) {
    val writer = BufferedWriter(FileWriter(path))
    try {
        writer.write(content)
    } finally {
        writer.close()
    }
}

fun copyFile(source: String, target: String) {
    val input = FileInputStream(source)
    try {
        val output = FileOutputStream(target)
        try {
            input.copyTo(output)
        } finally {
            output.close()
        }
    } finally {
        input.close()
    }
}

Kotlin-style (после):

fun readFile(path: String): String =
    BufferedReader(FileReader(path)).use { it.readText() }

fun writeToFile(path: String, content: String) {
    BufferedWriter(FileWriter(path)).use { it.write(content) }
}

fun copyFile(source: String, target: String) {
    FileInputStream(source).use { input ->
        FileOutputStream(target).use { output ->
            input.copyTo(output)
        }
    }
}

// Even better - use Kotlin's File API
fun readFile(path: String): String =
    File(path).readText()

fun writeToFile(path: String, content: String) {
    File(path).writeText(content)
}

fun copyFile(source: String, target: String) {
    File(source).copyTo(File(target))
}