meldestelle/.junie/guidelines/project-standards/coding-standards.md
2025-09-15 12:49:55 +02:00

6.9 KiB

Coding Standards und Code-Qualität


guideline_type: "project-standards" scope: "coding-standards" audience: ["developers", "ai-assistants"] last_updated: "2025-09-15" dependencies: ["master-guideline.md"] related_files: ["build.gradle.kts", "detekt.yml", "*.kt"] ai_context: "Coding conventions, naming standards, type safety, error handling, and logging practices"

📋 Coding Conventions & Code-Qualität

Sprach- und Stilstandards

  • Primärsprache: Kotlin (JVM/Multiplatform)
  • Java-Kompatibilität: Ziel ist Java 21+
  • Code-Stil: Offizielle Kotlin Coding Conventions, durch Detekt geprüft.

🤖 AI-Assistant Hinweis: Alle Kotlin-Code muss den offiziellen Kotlin Coding Conventions entsprechen:

  • Detekt-Validierung: Automatische Code-Style-Prüfung
  • Java 21+ Kompatibilität: Nutze moderne Java-Features wo sinnvoll
  • Multiplatform: Code sollte plattformübergreifend funktionieren

Namenskonventionen

  • Klassen & Interfaces: PascalCase (z.B. MemberService, EventRepository)
  • Funktionen & Variablen: camelCase (z.B. authenticateUser, memberRepository)
  • Testmethoden: Beschreibend mit Backticks (z.B. `should return Success for valid credentials`)
  • Konstanten: SCREAMING_SNAKE_CASE (z.B. MAX_RETRY_ATTEMPTS)
  • Enums: PascalCase für Werte (z.B. MemberStatus.ACTIVE)

Value Classes für Typsicherheit

Primitive Typen (UUID, String, Long) für IDs oder spezifische Werte müssen in typsichere value class-Wrapper gekapselt werden.

@JvmInline
value class MemberId(val value: UUID) {
    companion object {
        fun of(value: String): Result<MemberId, ValidationError> =
            runCatching { UUID.fromString(value) }
                .map { MemberId(it) }
                .mapError { ValidationError.INVALID_UUID }
    }
}

Error-Handling & Logging

  • Result-Pattern: Für erwartbare Geschäftsfehler ist das Result-Pattern zu verwenden. Exceptions sind für unerwartete, technische Fehler reserviert.

  • Fehler-Hierarchie: Wir verwenden eine sealed class-Hierarchie, um Fehlerarten klar zu kategorisieren (DomainError, ValidationError, BusinessError, TechnicalError).

  • Structured Logging: Logs müssen strukturiert sein und eine Korrelations-ID enthalten, um Anfragen über Service-Grenzen hinweg zu verfolgen.

logger.info {
    "Creating member" with mapOf(
        "memberId" to command.memberId.value,
        "correlationId" to MDC.get("correlationId")
    )
}

🎯 AI-Assistenten: Coding-Standards-Schnellreferenz

Namenskonventionen-Übersicht

Element Convention Beispiel
Klassen/Interfaces PascalCase MemberService, EventRepository
Funktionen/Variablen camelCase authenticateUser, memberRepository
Konstanten SCREAMING_SNAKE_CASE MAX_RETRY_ATTEMPTS
Test-Methoden Backticks beschreibend `should return Success for valid credentials`
Enum-Werte PascalCase MemberStatus.ACTIVE

Code-Qualitäts-Checkliste

  • Detekt-Prüfung: Code-Stil entspricht Kotlin Conventions
  • Value Classes: Primitive Typen sind in typsichere Wrapper gekapselt
  • Result-Pattern: Geschäftsfehler verwenden Result statt Exceptions
  • Structured Logging: Logs enthalten Korrelations-IDs
  • Error-Hierarchie: Sealed Classes für Fehlerkategorisierung

Häufige Code-Patterns

Typsichere IDs

@JvmInline
value class EntityId(val value: UUID) {
    companion object {
        fun generate(): EntityId = EntityId(UUID.randomUUID())
        fun of(value: String): Result<EntityId, ValidationError> =
            runCatching { UUID.fromString(value) }
                .map { EntityId(it) }
                .mapError { ValidationError.INVALID_UUID }
    }
}

Error-Handling mit Result

interface EntityRepository {
    suspend fun findById(id: EntityId): Result<Entity?, RepositoryError>
    suspend fun save(entity: Entity): Result<Unit, RepositoryError>
}

// Verwendung
when (val result = repository.findById(entityId)) {
    is Result.Success -> processEntity(result.value)
    is Result.Failure -> handleError(result.error)
}

Structured Logging

class EntityService {
    private val logger = LoggerFactory.getLogger(EntityService::class.java)

    suspend fun processEntity(command: ProcessEntityCommand): Result<Unit, ProcessingError> {
        val correlationId = MDC.get("correlationId")

        logger.info {
            "Processing entity" with mapOf(
                "entityId" to command.entityId.value,
                "correlationId" to correlationId,
                "operation" to "process"
            )
        }

        return try {
            // Processing logic
            Result.Success(Unit)
        } catch (e: Exception) {
            logger.error {
                "Entity processing failed" with mapOf(
                    "entityId" to command.entityId.value,
                    "correlationId" to correlationId,
                    "error" to e.message
                )
            }
            Result.Failure(ProcessingError.TECHNICAL_ERROR)
        }
    }
}

Sealed Class Hierarchie für Fehler

sealed interface DomainError {
    val message: String
    val code: String
}

sealed interface ValidationError : DomainError {
    data object INVALID_UUID : ValidationError {
        override val message = "Invalid UUID format"
        override val code = "VALIDATION_INVALID_UUID"
    }

    data object REQUIRED_FIELD_MISSING : ValidationError {
        override val message = "Required field is missing"
        override val code = "VALIDATION_REQUIRED_FIELD_MISSING"
    }
}

sealed interface BusinessError : DomainError {
    data object ENTITY_NOT_FOUND : BusinessError {
        override val message = "Entity not found"
        override val code = "BUSINESS_ENTITY_NOT_FOUND"
    }
}

sealed interface TechnicalError : DomainError {
    data object DATABASE_CONNECTION_FAILED : TechnicalError {
        override val message = "Database connection failed"
        override val code = "TECHNICAL_DATABASE_CONNECTION_FAILED"
    }
}

Detekt-Konfiguration

Wichtige Detekt-Regeln für das Projekt:

# detekt.yml
style:
  MaxLineLength:
    maxLineLength: 120
  FunctionNaming:
    functionPattern: '^[a-z][a-zA-Z0-9]*$'
  ClassNaming:
    classPattern: '^[A-Z][a-zA-Z0-9]*$'

complexity:
  ComplexMethod:
    threshold: 15
  LongParameterList:
    functionThreshold: 6

potential-bugs:
  UnsafeCallOnNullableType:
    active: true

Navigation: