Loading...
Loading...
Compare original and translation side by side
Applies to: Kotlin 1.9+, JVM 17+, Coroutines, Android, Server-side
适用范围:Kotlin 1.9+、JVM 17+、协程、Android、服务端
valvarvalvarbuild.gradle.kts./gradlew dependenciesbuild.gradle.kts./gradlew dependenciesktlintdetektcom.example.userservicePascalCasecamelCaseSCREAMING_SNAKE_CASEktlintdetektcom.example.userservicePascalCasecamelCaseSCREAMING_SNAKE_CASE!!?.?:requireNotNull()checkNotNull()!!?.let { }if// BAD: crashes at runtime
val length = name!!.length
// GOOD: safe call with fallback
val length = name?.length ?: 0
// GOOD: explicit contract with clear error
val validName = requireNotNull(name) { "User name must not be null for ID=$id" }!!?.?:requireNotNull()checkNotNull()!!?.let { }if// BAD: 运行时会崩溃
val length = name!!.length
// GOOD: 带兜底逻辑的安全调用
val length = name?.length ?: 0
// GOOD: 明确的契约与清晰的错误提示
val validName = requireNotNull(name) { "ID为$id的用户姓名不能为空" }coroutineScopesupervisorScopeGlobalScopewithContext(Dispatchers.IO)CancellationExceptionwithTimeoutwithTimeoutOrNull// BAD: unstructured, leaks coroutines
GlobalScope.launch { fetchData() }
// GOOD: structured concurrency, respects parent lifecycle
coroutineScope {
val user = async { userService.getUser(id) }
val orders = async { orderService.getOrders(id) }
UserWithOrders(user.await(), orders.await())
}coroutineScopesupervisorScopeGlobalScopewithContext(Dispatchers.IO)CancellationExceptionwithTimeoutwithTimeoutOrNull// BAD: 非结构化并发,会导致协程泄漏
GlobalScope.launch { fetchData() }
// GOOD: 结构化并发,遵循父级生命周期
coroutineScope {
val user = async { userService.getUser(id) }
val orders = async { orderService.getOrders(id) }
UserWithOrders(user.await(), orders.await())
}StringExtensions.ktDateExtensions.ktAny@receiverStringExtensions.ktDateExtensions.ktAny@receivermyproject/
├── app/ # Application module or main entry
│ └── src/main/kotlin/
├── domain/ # Business logic, entities, use cases
│ └── src/main/kotlin/
│ └── com/example/domain/
│ ├── model/ # Data classes, sealed classes
│ ├── repository/ # Repository interfaces
│ └── usecase/ # Business operations
├── data/ # Data layer implementations
│ └── src/main/kotlin/
│ └── com/example/data/
│ ├── repository/ # Repository implementations
│ ├── remote/ # API clients, DTOs
│ └── local/ # Database, DAOs
├── presentation/ # UI or API controllers
├── build.gradle.kts
├── settings.gradle.kts
└── gradle.propertiesdomain/data/domain/presentation/domain/data/myproject/
├── app/ # 应用模块或主入口
│ └── src/main/kotlin/
├── domain/ # 业务逻辑、实体、用例
│ └── src/main/kotlin/
│ └── com/example/domain/
│ ├── model/ # 数据类、密封类
│ ├── repository/ # 仓库接口
│ └── usecase/ # 业务操作
├── data/ # 数据层实现
│ └── src/main/kotlin/
│ └── com/example/data/
│ ├── repository/ # 仓库实现
│ ├── remote/ # API客户端、DTO
│ └── local/ # 数据库、DAO
├── presentation/ # UI或API控制器
├── build.gradle.kts
├── settings.gradle.kts
└── gradle.propertiesdomain/data/domain/presentation/domain/data/data class User(
val id: UserId,
val email: Email,
val name: String,
val role: Role = Role.VIEWER,
) {
init {
require(name.isNotBlank()) { "User name must not be blank" }
}
}
// Value classes for type-safe IDs (zero runtime overhead)
@JvmInline
value class UserId(val value: String) {
init { require(value.isNotBlank()) { "UserId must not be blank" } }
}data class User(
val id: UserId,
val email: Email,
val name: String,
val role: Role = Role.VIEWER,
) {
init {
require(name.isNotBlank()) { "用户姓名不能为空" }
}
}
// 类型安全ID的值类(零运行时开销)
@JvmInline
value class UserId(val value: String) {
init { require(value.isNotBlank()) { "UserId不能为空" } }
}sealed interface Result<out T> {
data class Success<T>(val data: T) : Result<T>
data class Failure(val error: AppError) : Result<Nothing>
}
sealed class AppError(val message: String) {
data class NotFound(val resource: String, val id: String) :
AppError("$resource with ID $id not found")
data class Validation(val field: String, val reason: String) :
AppError("Validation failed for $field: $reason")
data class Unauthorized(val detail: String = "Authentication required") :
AppError(detail)
}
// Exhaustive when expressions
fun <T> Result<T>.getOrThrow(): T = when (this) {
is Result.Success -> data
is Result.Failure -> throw error.toException()
}sealed interface Result<out T> {
data class Success<T>(val data: T) : Result<T>
data class Failure(val error: AppError) : Result<Nothing>
}
sealed class AppError(val message: String) {
data class NotFound(val resource: String, val id: String) :
AppError("ID为$id的$resource不存在")
data class Validation(val field: String, val reason: String) :
AppError("$field校验失败: $reason")
data class Unauthorized(val detail: String = "需要身份认证") :
AppError(detail)
}
// 穷尽式when表达式
fun <T> Result<T>.getOrThrow(): T = when (this) {
is Result.Success -> data
is Result.Failure -> throw error.toException()
}| Function | Context | Returns | Use for |
|---|---|---|---|
| | Lambda result | Nullable transforms, scoped vars |
| | Lambda result | Compute value using object context |
| | Lambda result | Operate on non-null object |
| | Object itself | Configure/build an object |
| | Object itself | Side effects (logging, events) |
// apply: configure and return the object
val request = HttpRequest.Builder().apply {
url(endpoint)
header("Authorization", "Bearer $token")
timeout(Duration.ofSeconds(30))
}.build()
// also: side effects without modifying the chain
val savedUser = userRepository.save(newUser).also { user ->
logger.info("Created user: ${user.id}")
}| 函数 | 上下文 | 返回值 | 适用场景 |
|---|---|---|---|
| | Lambda返回值 | 可空类型转换、作用域变量 |
| | Lambda返回值 | 基于对象上下文计算值 |
| | Lambda返回值 | 操作非空对象 |
| | 对象本身 | 配置/构建对象 |
| | 对象本身 | 副作用操作(日志、事件) |
// apply: 配置并返回对象
val request = HttpRequest.Builder().apply {
url(endpoint)
header("Authorization", "Bearer $token")
timeout(Duration.ofSeconds(30))
}.build()
// also: 不修改调用链的副作用操作
val savedUser = userRepository.save(newUser).also { user ->
logger.info("创建用户: ${user.id}")
}*Test.ktsrc/test/kotlin/@Test@Nested@DisplayName@ParameterizedTest@MethodSourcerunTestkotlinx-coroutines-test*Test.ktsrc/test/kotlin/@Test@Nested@DisplayName@ParameterizedTest@MethodSourcekotlinx-coroutines-testrunTestclass UserServiceTest {
private val userRepository = mockk<UserRepository>()
private val eventBus = mockk<EventBus>(relaxed = true)
private val service = UserService(userRepository, eventBus)
@Nested
@DisplayName("createUser")
inner class CreateUser {
@Test
fun `creates user with valid input`() = runTest {
coEvery { userRepository.save(any()) } returns mockUser
val result = service.createUser(validInput)
assertThat(result).isInstanceOf(Result.Success::class.java)
coVerify { userRepository.save(any()) }
coVerify { eventBus.publish(any<UserCreatedEvent>()) }
}
@Test
fun `fails with blank name`() = runTest {
val result = service.createUser(blankNameInput)
assertThat(result).isInstanceOf(Result.Failure::class.java)
coVerify(exactly = 0) { userRepository.save(any()) }
}
}
}class UserServiceTest {
private val userRepository = mockk<UserRepository>()
private val eventBus = mockk<EventBus>(relaxed = true)
private val service = UserService(userRepository, eventBus)
@Nested
@DisplayName("createUser方法")
inner class CreateUser {
@Test
fun `输入合法时创建用户成功`() = runTest {
coEvery { userRepository.save(any()) } returns mockUser
val result = service.createUser(validInput)
assertThat(result).isInstanceOf(Result.Success::class.java)
coVerify { userRepository.save(any()) }
coVerify { eventBus.publish(any<UserCreatedEvent>()) }
}
@Test
fun `姓名为空时创建失败`() = runTest {
val result = service.createUser(blankNameInput)
assertThat(result).isInstanceOf(Result.Failure::class.java)
coVerify(exactly = 0) { userRepository.save(any()) }
}
}
}companion object {
@JvmStatic
fun emailCases() = listOf(
Arguments.of("user@example.com", true),
Arguments.of("invalid-email", false),
Arguments.of("", false),
)
}
@ParameterizedTest(name = "email \"{0}\" valid={1}")
@MethodSource("emailCases")
fun `validates email format`(email: String, expected: Boolean) {
val result = runCatching { Email(email) }
assertThat(result.isSuccess).isEqualTo(expected)
}companion object {
@JvmStatic
fun emailCases() = listOf(
Arguments.of("user@example.com", true),
Arguments.of("invalid-email", false),
Arguments.of("", false),
)
}
@ParameterizedTest(name = "邮箱\"{0}\"校验结果={1}")
@MethodSource("emailCases")
fun `校验邮箱格式`(email: String, expected: Boolean) {
val result = runCatching { Email(email) }
assertThat(result.isSuccess).isEqualTo(expected)
}./gradlew build # Compile + test + check
./gradlew test # Run all tests
./gradlew test --tests "*.UserServiceTest" # Specific test class
./gradlew koverReport # Coverage report
./gradlew ktlintCheck # Check formatting
./gradlew ktlintFormat # Auto-fix formatting
./gradlew detekt # Static analysis
./gradlew dependencies # Dependency tree./gradlew build # 编译 + 测试 + 检查
./gradlew test # 运行所有测试
./gradlew test --tests "*.UserServiceTest" # 运行指定测试类
./gradlew koverReport # 生成覆盖率报告
./gradlew ktlintCheck # 检查代码格式
./gradlew ktlintFormat # 自动修复代码格式
./gradlew detekt # 静态分析
./gradlew dependencies # 生成依赖树// build.gradle.kts
plugins {
kotlin("jvm") version "1.9.22"
id("org.jlleitschuh.gradle.ktlint") version "12.1.0"
id("io.gitlab.arturbosch.detekt") version "1.23.4"
id("org.jetbrains.kotlinx.kover") version "0.7.5"
}
kotlin { jvmToolchain(17) }
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
testImplementation(kotlin("test"))
testImplementation("io.mockk:mockk:1.13.9")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0")
testImplementation("org.assertj:assertj-core:3.25.1")
}
detekt {
config.setFrom("detekt.yml")
buildUponDefaultConfig = true
}
kover {
verify { rule { minBound(80) } }
}// build.gradle.kts
plugins {
kotlin("jvm") version "1.9.22"
id("org.jlleitschuh.gradle.ktlint") version "12.1.0"
id("io.gitlab.arturbosch.detekt") version "1.23.4"
id("org.jetbrains.kotlinx.kover") version "0.7.5"
}
kotlin { jvmToolchain(17) }
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
testImplementation(kotlin("test"))
testImplementation("io.mockk:mockk:1.13.9")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0")
testImplementation("org.assertj:assertj-core:3.25.1")
}
detekt {
config.setFrom("detekt.yml")
buildUponDefaultConfig = true
}
kover {
verify { rule { minBound(80) } }
}undefinedundefinedundefinedundefined