Slide 1

Slide 1 text

Harmonizing Kotlin codebase with Konsist Igor Wojda @igorwojda Natalia Peterwas

Slide 2

Slide 2 text

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 

Slide 3

Slide 3 text

L I N T E R S 

Slide 4

Slide 4 text

Codebase quality  Code Readability  Adherence to coding standards  Improve productivity  Prevent bugs  LINTERS

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

MEET JANE Passionate Developer

Slide 7

Slide 7 text

FIRST TASK Let’s Code

Slide 8

Slide 8 text

DISCOVERY Explore the Code Base

Slide 9

Slide 9 text

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 } }

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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”

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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)

Slide 16

Slide 16 text

M E E T K O N S I S T

Slide 17

Slide 17 text

Create The Scope STRUCTURE OF KONSIST TEST Filter The Declarations Define Assertion 1 2 3

Slide 18

Slide 18 text

1. CREATE THE SCOPE

Slide 19

Slide 19 text

SCOPE Konsist.scopeFromProject() Scope contains all *.kt files present in project.

Slide 20

Slide 20 text

SCOPE Konsist.scopeFromModule("app") Each scope contains all *.kt files present in module. Konsist.scopeFromModule("ui")

Slide 21

Slide 21 text

SCOPE Scope contains all *.kt files from “integrationTest” source set. Konsist.scopeFromSourceSet("integrationTest")

Slide 22

Slide 22 text

SCOPE Konsist.scopeFromProduction()

Slide 23

Slide 23 text

val customScope = appScope + uiScope SCOPE COMPOSITION val appScope = Konsist.scopeFromModule(“app") val uiScope = Konsist.scopeFromModule(“ui")

Slide 24

Slide 24 text

2. FILTER DECLARATIONS

Slide 25

Slide 25 text

Konsist .scopeFromProduction() FILTER DECLARATIONS

Slide 26

Slide 26 text

Konsist .scopeFromProduction() .classes() FILTER DECLARATIONS List

Slide 27

Slide 27 text

Konsist .scopeFromProduction() .classes() FILTER DECLARATIONS .filter { it.name.endsWith(“Repository") } .withNameEndingWith("Repository")

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Konsist .scopeFromProduction() .functions() .withPackage("com.app.data") .withExpressionBody() .withParameters { parameters -> parameters.any { it.representsTypeOf() } } .withReturnType { it.isKotlinType } FILTER DECLARATIONS Konsist Test

Slide 30

Slide 30 text

3. DEFINE ASSERTION

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Konsist .scopeFromProduction() .classes() .withNameEndingWith("Repository") .assertTrue { it.resideInPackage("..repository..") } ASSERT Check multiple classes Last expression determines test outcome

Slide 33

Slide 33 text

@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..") }

Slide 34

Slide 34 text

U N Y F Y I N G P R O J E C T U S E C A S E S

Slide 35

Slide 35 text

EVERY USE CASE CLASS I S D E F I N E D I N S I D E “USECASE” PACKAGE FIRST TEST

Slide 36

Slide 36 text

CHECK CLASS NAME @Test fun `every use case has name ending with 'UseCase'`() { Konsist .scopeFromProduction() .classes() .filter { it.resideInPackage("..usecase..") } .assertTrue { it.hasNameEndingWith("UseCase") } }

Slide 37

Slide 37 text

CHECK CLASS NAME public class MarkSongAsFavorite { // … } class CreatePlaylistUC { // … } MarkSongAsFavoriteUseCase { CreatePlaylistUseCase {

Slide 38

Slide 38 text

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" } } }

Slide 39

Slide 39 text

CHECK “INVOKE” METHOD public class MarkSongAsFavoriteUseCase { fun execute() { // business logic } } class CreatePlaylistUseCase { fun run() { // business logic } // ... } invoke() { invoke() {

Slide 40

Slide 40 text

@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" } } }

Slide 41

Slide 41 text

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'`() {

Slide 42

Slide 42 text

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() {

Slide 43

Slide 43 text

CHECK SINGLE PUBLIC METHOD @Test fun `every use case has single public method`() { useCaseScope .assertTrue { it.countFunctions { item -> item.hasPublicOrDefaultModifier } == 1 } }

Slide 44

Slide 44 text

CHECK SINGLE PUBLIC METHOD class CreatePlaylistUseCase { operator fun invoke() { // business logic } fun calculateCalories() { // business logic } } private fun calculateCalories() {

Slide 45

Slide 45 text

CLASS VISIBILITY @Test fun `every use case class is public'`() { useCaseScope .assertTrue { it.hasPublicOrDefaultModifier } }

Slide 46

Slide 46 text

CLASS VISIBILITY private class ShareSongUseCase { operator fun invoke() { // business logic } } class ShareSongUseCase {

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

A R C H I T E C T U R E C H E C K S

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

A N O T H E R L I N T E R

Slide 51

Slide 51 text

• 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

Slide 52

Slide 52 text

• 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

Slide 53

Slide 53 text

JAVA BYTECODE Java Source Code Kotlin Source Code Java Compiler Kotlin Compiler JVM Bytecode

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

FILTER DECLARATIONS @Test fun `every class should have a test class`() { } extensions default arguments data classes sealed interfaces companion objects

Slide 56

Slide 56 text

JAVA BYTECODE Java Source Code Kotlin Source Code Java Compiler Kotlin Compiler JVM Bytecode

Slide 57

Slide 57 text

KOTLIN MULTIPLATFORM JVM Bytecode Native WebAssembly JavaScript Kotlin Source Code   x x x    Targets

Slide 58

Slide 58 text

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("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)) } } }

Slide 59

Slide 59 text

K O N S I S T D E E P D I V E

Slide 60

Slide 60 text

KoFunction KoProperty KO DECLARATION KoScope KoClass KoFunction KoFile Konsist .scopeFromProject() .classes() .functions()

Slide 61

Slide 61 text

DEBUGGING Konsist .scopeFromProduction() .functions() .print() .print(prefix = “KonsistTest") .print { it.name } .withExpressionBody() .assertTrue { it.name }

Slide 62

Slide 62 text

DEBUGGING

Slide 63

Slide 63 text

DEBUGGING

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

S T A T E O F K O N S I S T

Slide 66

Slide 66 text

KONSIST IN NUMBERS Lines Of Code 180K Github Stars 900  Community Members 150+ Versions Released 10 PRs Closed 1K Contributors 50 Doc pages 150

Slide 67

Slide 67 text

DOCUMENTATION docs.konsist.lemonappdev.com

Slide 68

Slide 68 text

KONSIST COMPATIBILITY Testing Frameworks Projects Types Build Systems Multiplatform

Slide 69

Slide 69 text

EXTENSIVE TESTING

Slide 70

Slide 70 text

PRODUCTION READY Test Only Dependency

Slide 71

Slide 71 text

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 

Slide 72

Slide 72 text

WHERE TO START?  docs.konsist.lemonappdev.com Examine Documentation

Slide 73

Slide 73 text

THANKS FOR LISTENING! Any Questions? 

Slide 74

Slide 74 text

@igorwojda Thank you,
 and please vote Natalia Peterwas Igor Wojda 