Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Harmonizing Kotlin codebase with Konsist

Harmonizing Kotlin codebase with Konsist

Jane had always been passionate about coding, and the opportunity to work as a Kotlin developer was a dream come true. Shortly after starting a new job, Jane was assigned her first significant task: adding a new use case to the project. Excitement bubbled within her. This was a chance to prove herself, to make a meaningful impact. The task seemed straightforward enough, but she wanted to make sure she aligned her work with the established patterns of the project codebase. So, Jane began exploring the existing use cases, opening file after file, and digging into the structures and logic. She expected to find a consistent pattern in the project codebase, something that would guide her in crafting the new use case. But what she found was a bit overwhelming — every use case was different…

We've all been in this situation — stepping into a new project and taking a moment to build a mental picture of the project. The project's complexity is often attributed to a code base that's riddled with inconsistencies, tangled connections, and disarray.

What if we could quickly create rules for guarding project consistency? Imagine being able to swiftly enforce coding standards tailored to your project and modify them with ease when necessary.

Embark on a journey with Konsist: the next-generation structural linter destined to revolutionize Kotlin code consistency. Imagine a tool so sharp, it transforms your coding conventions into a symphony of synchronized tests. Konsist goes beyond standard linting tools; Konsist is your bespoke guardian of tailored code quality.

Konsist unparalleled flexibility allows you to customize checks and conventions to fit the unique style of your project, whether it's enterprise-scale, open-source, or the next disruptive tech innovation.

Join us as we explore Konsist's unique features and demonstrate how Konsist can automate codebase consistency checks like never before. Unlock the potential of uniform coding practices; learn how Konsist can be the cornerstone of maintaining a Konsistent codebase.

Igor Wojda

May 23, 2024
Tweet

More Decks by Igor Wojda

Other Decks in Programming

Transcript

  1. COMPLEX PROJECTS Poor Codebase Quality No Coding Standards  Codebase

    Growth  Team Changes  New features + Bug Fixes  Time Pressure  Personal Preferences  Poor PR Reviews  No Big Picture  No Automated Testing  No Documentation  Tech Debt 
  2. Codebase quality  Code Readability  Adherence to coding standards

     Improve productivity  Prevent bugs  LINTERS
  3. KOTLIN LINTERS • Code Maintainability • Complexity Reduction • Code

    Safety Popular linters in Kotlin Ecosystem Lint • Code Readability • Style Consistency • Structural Clarity • Compatibility Awareness • Performance Efficiency • API Adherence
  4. EXISTING USE CASES  USE CASES Use Case 1 public

    class MarkSongAsFavorite { fun execute() { // business logic } } class CreatePlaylistUC { fun run() { // business logic } fun calculateCalories() { // business logic } } Use Case 3 Use Case 2 private class ShareSongUseCase { operator fun invoke() { // business logic } } Use Case 4 class SubscribeToArtisUseCase { fun invoke() { // business logic } }
  5. USE CASE SUFFIXES Use Case 1 public class MarkSongAsFavorite {

    fun execute() { // business logic } } No Suffix Use Case 2 private class ShareSongUseCase { operator fun invoke() { // business logic } } Use Case 4 class SubscribeToArtisUseCase { fun invoke() { // business logic } } “UseCase” Suffix class CreatePlaylistUC { fun run() { // business logic } fun calculateCalories() { // business logic } } Use Case 3 “UC” Suffix
  6. USE CASE METHOD NAMES Use Case 1 public class MarkSongAsFavorite

    { fun execute() { // business logic } } “execute” Method Use Case 2 private class ShareSongUseCase { operator fun invoke() { // business logic } } Use Case 4 class SubscribeToArtisUseCase { fun invoke() { // business logic } } “invoke” Method class CreatePlaylistUC { fun run() { // business logic } fun calculateCalories() { // business logic } } Use Case 3 “run” Method
  7. USE CASE NUM PUBLIC METHODS Use Case 1 public class

    MarkSongAsFavorite { fun execute() { // business logic } } Use Case 2 private class ShareSongUseCase { operator fun invoke() { // business logic } } Use Case 4 class SubscribeToArtisUseCase { fun invoke() { // business logic } } 1 public Method class CreatePlaylistUC { fun run() { // business logic } fun calculateCalories() { // business logic } } Use Case 3 2 public Methods
  8. USE CASE CLASS VISIBILITY MODIFIERS Use Case 1 public class

    MarkSongAsFavorite { fun execute() { // business logic } } Explicit “public” Use Case 2 private class ShareSongUseCase { operator fun invoke() { // business logic } } Explicit “private” Use Case 4 class SubscribeToArtisUseCase { fun invoke() { // business logic } } class CreatePlaylistUC { fun run() { // business logic } fun calculateCalories() { // business logic } } Use Case 3 Implicit “public”
  9. USE CASE OPERATOR MODIFIER Use Case 1 public class MarkSongAsFavorite

    { fun execute() { // business logic } } Use Case 2 private class ShareSongUseCase { operator fun invoke() { // business logic } } Use Case 4 class SubscribeToArtisUseCase { fun invoke() { // business logic } } 1 Operator Modifier class CreatePlaylistUC { fun run() { // business logic } fun calculateCalories() { // business logic } } Use Case 3 3 No Operator Modifier
  10. USECASE DECLARATIONS Suffix Method Name Operator Num Public Methods Class

    Visibility MarkSongAsFavorite None execute No 1 Public (explicit) ShareSong UseCase invoke Yes 1 Private CreatePlaylist UC run No 2 Public (implicit) SubscribeToArtist UseCase invoke No 1 Public (implicit)
  11. val customScope = appScope + uiScope SCOPE COMPOSITION val appScope

    = Konsist.scopeFromModule(“app") val uiScope = Konsist.scopeFromModule(“ui")
  12. Konsist .scopeFromProduction() .classes() .withAllAnnotationsOf( CachingService::class, ValidationService::class ) .withInternalModifier() .withSecondaryConstructors() .withParentClassOf(Service::class)

    @CachingService @ValidationService internal class PhotoService : Service { // code constructor(cache: LocalCache) { // code } } FILTER DECLARATIONS .without... .with... { it. } Konsist Test Codebase
  13. Konsist .scopeFromProduction() .functions() .withPackage("com.app.data") .withExpressionBody() .withParameters { parameters -> parameters.any

    { it.representsTypeOf<Service>() } } .withReturnType { it.isKotlinType } FILTER DECLARATIONS Konsist Test
  14. ASSERTIONS @Repository public open class Car constructor() { val speed

    = 0 open suspend fun startEngine(fuelType: FuelType) { // .. } } Methods .name .properties .functions .constructors .hasNameContaining .hasBlockBody .isTopLevel .hasOpenModifier Properties
  15. @Test fun `test name`() { Konsist .scopeFromProduction() .classes() .withAllAnnotationsOf(Repository::class) .assertTrue

    { it.resideInPackage("..repository..") } } “com.lemonappdev:konsist:0.15.1” “Test framework dependency” Add to test source set RUNNING KONSIST TEST Konsist .scopeFromProduction() .classes() .withAllAnnotationsOf(Repository::class) .assertTrue { it.resideInPackage("..repository..") } class SampleKonsistTest : FreeSpec({ “test name" { Konsist .scopeFromProduction() .classes() .withAllAnnotationsOf(Repository::class) .assertTrue { it.resideInPackage("..repository..") } } }) Konsist .scopeFromProduction() .classes() .withAllAnnotationsOf(Repository::class) .assertTrue { it.resideInPackage("..repository..") }
  16. U N Y F Y I N G P R

    O J E C T U S E C A S E S
  17. EVERY USE CASE CLASS I S D E F I

    N E D I N S I D E “USECASE” PACKAGE FIRST TEST
  18. CHECK CLASS NAME @Test fun `every use case has name

    ending with 'UseCase'`() { Konsist .scopeFromProduction() .classes() .filter { it.resideInPackage("..usecase..") } .assertTrue { it.hasNameEndingWith("UseCase") } }
  19. CHECK CLASS NAME public class MarkSongAsFavorite { // … }

    class CreatePlaylistUC { // … } MarkSongAsFavoriteUseCase { CreatePlaylistUseCase {
  20. CHECK “INVOKE” METHOD @Test fun `every use case has method

    named 'invoke'`() { Konsist .scopeFromProduction() .classes() .filter { it.resideInPackage("..usecase..") } .assertTrue { it.hasFunction { function -> function.name == "invoke" } } }
  21. CHECK “INVOKE” METHOD public class MarkSongAsFavoriteUseCase { fun execute() {

    // business logic } } class CreatePlaylistUseCase { fun run() { // business logic } // ... } invoke() { invoke() {
  22. @Test fun `every use case has name ending with 'UseCase'`()

    { Konsist .scopeFromProduction() .classes() .filter { it.resideInPackage("..usecase..") } .assertTrue it.hasNameEndingWith("UseCase") } } @Test fun `every use case method named 'invoke'`() { Konsist .scopeFromProduction() .classes() .filter { it.resideInPackage("..usecase..") } .assertTrue { it.hasFunction { function -> function.name == "invoke" } } } REFACTOR private val useCaseScope = Konsist .scopeFromProject() .classes() .filter { it.resideInPackage("..usecase..") } @Test fun `every use case has name ending with 'UseCase'`() { useCaseScope .assertTrue it.hasNameEndingWith("UseCase") } } @Test fun `every use case method named 'invoke'`() { useCaseScope .assertTrue { it.hasFunction { function -> function.name == "invoke" } } }
  23. CHECK OPERATOR MODIFIER @Test fun `every use case method named

    'invoke'`() { useCaseScope .assertTrue { it.hasFunction { function -> function.name == “invoke" } } } function.hasOperatorModifier && fun `every use case has operator method named 'invoke'`() {
  24. CHECK OPERATOR MODIFIER public class MarkSongAsFavoriteUseCase { fun invoke() {

    // business logic } } class SubscribeToArtisUseCase { fun invoke() { // business logic } } class CreatePlaylistUseCase { fun invoke() { // business logic } // .. } operator fun invoke() { operator fun invoke() { operator fun invoke() {
  25. CHECK SINGLE PUBLIC METHOD @Test fun `every use case has

    single public method`() { useCaseScope .assertTrue { it.countFunctions { item -> item.hasPublicOrDefaultModifier } == 1 } }
  26. CHECK SINGLE PUBLIC METHOD class CreatePlaylistUseCase { operator fun invoke()

    { // business logic } fun calculateCalories() { // business logic } } private fun calculateCalories() {
  27. CLASS VISIBILITY @Test fun `every use case class is public'`()

    { useCaseScope .assertTrue { it.hasPublicOrDefaultModifier } }
  28. CLASS VISIBILITY private class ShareSongUseCase { operator fun invoke() {

    // business logic } } class ShareSongUseCase {
  29. USECASE DECLARATIONS Suffix Method Name Operator Num Public Methods Class

    Visibility MarkSongAsFavorite UseCase invoke Yes 1 Public ShareSong UseCase invoke Yes 1 Public CreatePlaylist UseCase invoke Yes 1 Public SubscribeToArtist UseCase invoke Yes 1 Public
  30. A R C H I T E C T U

    R E C H E C K S
  31. STRUCTURE OF KONSIST TEST Konsist .scopeFromProduction() .assertArchitecture { // Define

    layers val domain = Layer("Domain", "com.app.domain..") val presentation = Layer("Presentation", "com.app.presentation..") val data = Layer("Data", "com.app.data..") // Define architecture assertions domain.dependsOnNothing() presentation.dependsOn(domain) data.dependsOn(domain) } Presentation Data Domain
  32. • Code Maintainability • Complexity Reduction • Code Safety Popular

    linters in Kotlin Ecosystem Lint • Code Readability • Style Consistency • Structural Clarity • Compatibility Awareness • Performance Efficiency • API Adherence KOTLIN LINTERS
  33. • Code Maintainability • Complexity Reduction • Code Safety Popular

    linters in Kotlin Ecosystem Lint • Code Readability • Style Consistency • Structural Clarity • Compatibility Awareness • Performance Efficiency • API Adherence KOTLIN LINTERS • Architectural Integrity • Layer Separation • Testability & Scalability
  34. SINGLE METHOD CHECK Overload 1 (no arguments) Overload 2 (one

    argument) Overload 3 (all arguments) fun greet(name: String = "World", greeting: String = "Hello") fun greet() Kotlin Source Code JVM Bytecode Method
  35. FILTER DECLARATIONS @Test fun `every class should have a test

    class`() { } extensions default arguments data classes sealed interfaces companion objects
  36. ARCHUNIT VERBOSE SYNTAX @Test fun `classes with 'UseCase' suffix should

    have single method named ‘invoke'`() { classes() .that().haveSimpleNameEndingWith("UseCase") .should(haveOnePublicMethodWithName("invoke")) .check(allClasses) } fun haveOnePublicMethodWithName(methodName: String) = object : ArchCondition<JavaClass>("have exactly one public method named '$methodName'") { override fun check(javaClass: JavaClass, conditionEvents: ConditionEvents) { val publicMethods = javaClass .methods .filter { it.modifiers.contains(JavaModifier.PUBLIC) } if (publicMethods.size == 1) { val method = publicMethods[0] // When method accepts Kotlin value class then Kotlin will generate // a random suffix to the method name e.g. methodName-suffix val methodExists = method.name == methodName || method.name.startsWith("$methodName-") if (!methodExists) { val message: String = createMessage( javaClass, "contains does not have method named '${method.name}' ${method.sourceCodeLocation}", ) conditionEvents.add(violated(javaClass, message)) } } else { val message: String = createMessage( javaClass, "contains multiple public methods", ) conditionEvents.add(violated(javaClass, message)) } } }
  37. DYNAMIC TESTS @Test fun `use case should have test`() {

    // Konsist Test } @Test fun `use case should reside in ..usecase.. package`() { // Konsist Test } Static Konsist Test Dynamic Konsist Test
  38. KONSIST IN NUMBERS Lines Of Code 180K Github Stars 900

     Community Members 150+ Versions Released 10 PRs Closed 1K Contributors 50 Doc pages 150
  39. Structural Linter 06 Production Ready  05 Free To Use

     04 Declaration and Architecture Verification  03 Flexible API  02 Open Source  KEY TAKEAWAYS 01 Kotlin Dedicated 