Конспект 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))
}