Slide 1

Slide 1 text

KotlinのLinter まなびなおし2024 Kotlin Fest 2024 @nyafunta9858

Slide 2

Slide 2 text

小林 慶弘 (Yoshihiro Kobayashi) a.k.a nyafunta9858 ● Android Engineer @ Yappli, Inc. ● 趣味:ガジェット集め、ゲーム、カメラ ...

Slide 3

Slide 3 text

本セッションについて Linterの導入状況というと... ● まだ導入されていない ● 認識しているが活用できていない ● メンテナンスされていない ● 形骸化してしまっている など

Slide 4

Slide 4 text

本セッションについて 想定対象者 ● なんとなくでLinterを選んでいる ● 自分たちに合ったLinterを選択できるようになりたい ● 最近見かけるアーキテクチャの検査、宣言の検査について興味がある 目標 ● Kotlin向けのLinterの特徴や利用方法を再履修 ● 活用シーンの比較・検討を通して「なんとなく」から「意識的な利用」へ

Slide 5

Slide 5 text

本セッションについて 注意書き ● カスタムルールの実装 How toなど深く踏み込んだ話は含まれません ● ツール本来の設計思想や思惑からは外れた提案も含まれているかもしれません ● そういった点も含め、議論のネタのひとつとしてお楽しみいただけますと幸いです

Slide 6

Slide 6 text

Linterとは

Slide 7

Slide 7 text

Linterとは Lint is the computer science term for a static code analysis tool used to flag programming errors, bugs, stylistic errors and suspicious constructs. The term originates from a Unix utility that examined C language source code. A program which performs this function is also known as a "linter". Wikipedia “Lint”

Slide 8

Slide 8 text

Linterとは ● コーディングルールの遵守による可読性の向上 ● 不具合の兆候(コードスメル)の検知・解消による品質保証 ● アーキテクチャの依存関係・依存方向に準拠による保守性の向上 ● 安心して開発するためのガードレール ● 課題領域本質へのレビューに集中するための手助け など

Slide 9

Slide 9 text

目次 ● 本セッションについて ● Linterとは ● ktlint ● detekt ● サンプルケースでの導入を考えてみる ● Konsist

Slide 10

Slide 10 text

ktlint

Slide 11

Slide 11 text

ktlint ● 目的:コードスタイル・フォーマットの準拠 ○ Kotlinのコーディング規約、 Android Kotlin Style Guideへの準拠 ● フォーマッタ(ソースコードの自動で整える機能)がビルトイン ● 標準で93種類のルールセットを用意 ● IntelliJ Plugin、Gradleほか複数のセットアップ環境を提供

Slide 12

Slide 12 text

ktlint -標準ルールセット- Annotation formatting Binary expression wrapping Blank line before declarations Block comment initial star alignment Chain method continuation Class signature Enum entry File name Final newline Function expression body Function literal Function signature Function type modifier spacing If else bracing Import ordering Indentation Naming Backing property naming Class naming Function naming Package name Property naming No blank lines in list No consecutive comments No empty file No empty first line at start in class body No single line block comment Ktlint-suppression rule Max line length Modifier order Multiline if-else Multiline loop No blank lines before } No blank lines in chained method calls No consecutive blank lines No empty ({}) class bodies No leading empty lines in method blocks No line break after else No line break before assignment No multi spaces No semicolons No trailing whitespaces No Unit as return type No unused imports No wildcard imports Spacing Angle bracket spacing Annotation spacing Blank line between declarations with annotations Blank line between declaration with comments Colon spacing Comma spacing Comment spacing Curly spacing Dot spacing Double colon spacing Function return type spacing Function start of body spacing Function type reference spacing Fun keyword spacing Kdoc wrapping Keyword spacing Modifier list spacing Nullable type spacing Operator spacing Parameter list spacing Parenthesis spacing Range spacing Spacing between function name and opening parenthesis Try catch finally spacing Type argument list spacing Type parameter list spacing Unary operator spacing String template String template indent Trailing comma on call site Trailing comma on declaration site Type argument comment Type parameter comment Unnecessary parenthesis before trailing lambda Value argument comment Value parameter comment Wrapping Argument list wrapping Chain wrapping Comment wrapping Condition wrapping Content receiver wrapping Enum wrapping If else wrapping Multiline expression wrapping Parameter list wrapping Parameter wrapping Property wrapping Statement wrapping Wrapping

Slide 13

Slide 13 text

ktlint -セットアップ- jlleitschuh/ktlint-gradle ● チェック・フォーマットタスクを自動生成、 Gradle Build Cacheサポートなど jeremymailen/kotlinter-gradle ● インクリメンタルビルドのサポート、 ktsファイルのサポート diffplug/spotless ● Kotlinに依らず広範な言語をサポートする Linter autostyle/autostyle ● spotlessからforkされたプロジェクト、 spotlessとは異なる機能追加

Slide 14

Slide 14 text

ktlint -セットアップ-

Slide 15

Slide 15 text

ktlint -セットアップ-

Slide 16

Slide 16 text

ktlint -セットアップ-

Slide 17

Slide 17 text

ktlint -カスタムルール-

Slide 18

Slide 18 text

detekt

Slide 19

Slide 19 text

detekt ● 目的:コード品質の向上や潜在的なバグの検出 ○ 複雑度、潜在的なパフォーマンスの問題、潜在的な不具合などの検出 ● フォーマッタは拡張機能として提供 ● 標準で302種類のルールセットを用意 ● IntelliJ Plugin、Gradleほか複数のセットアップ環境を提供

Slide 20

Slide 20 text

detekt -標準ルールセット- Comments Rule Set AbsentOrWrongFileLicense CommentOverPrivateFunction CommentOverPrivateProperty DeprecatedBlockTag EndOfSentenceFormat KDocReferencesNonPublicProperty OutdatedDocumentation UndocumentedPublicClass UndocumentedPublicFunction UndocumentedPublicProperty Complexity Rule Set CognitiveComplexMethod ComplexCondition ComplexInterface CyclomaticComplexMethod LabeledExpression LargeClass LongMethod LongParameterList MethodOverloading NamedArguments NestedBlockDepth NestedScopeFunctions ReplaceSafeCallChainWithRun StringLiteralDuplication TooManyFunctions Coroutines Rule Set GlobalCoroutineUsage InjectDispatcher RedundantSuspendModifier SleepInsteadOfDelay SuspendFunSwallowedCancellation SuspendFunWithCoroutineScopeReceiver SuspendFunWithFlowReturnType Empty-blocks Rule Set EmptyCatchBlock EmptyClassBlock EmptyDefaultConstructor EmptyDoWhileBlock EmptyElseBlock EmptyFinallyBlock EmptyForBlock EmptyFunctionBlock EmptyIfBlock EmptyInitBlock EmptyKtFile EmptySecondaryConstructor EmptyTryBlock EmptyWhenBlock EmptyWhileBlock Exceptions Rule Set ExceptionRaisedInUnexpectedLocation InstanceOfCheckForException NotImplementedDeclaration ObjectExtendsThrowable PrintStackTrace RethrowCaughtException ReturnFromFinally SwallowedException ThrowingExceptionFromFinally ThrowingExceptionInMain ThrowingExceptionsWithoutMessageOrCause ThrowingNewInstanceOfSameException TooGenericExceptionCaught TooGenericExceptionThrown Formatting Rule Set AnnotationOnSeparateLine AnnotationSpacing ArgumentListWrapping BlockCommentInitialStarAlignment ChainWrapping ClassName CommentSpacing CommentWrapping ContextReceiverMapping DiscouragedCommentLocation EnumEntryNameCase EnumWrapping Filename FinalNewline FunKeywordSpacing FunctionName FunctionReturnTypeSpacing FunctionSignature FunctionStartOfBodySpacing FunctionTypeReferenceSpacing IfElseBracing IfElseWrapping ImportOrdering Indentation KdocWrapping MaximumLineLength ModifierListSpacing ModifierOrdering MultiLineIfElse MultilineExpressionWrapping NoBlankLineBeforeRbrace NoBlankLineInList NoBlankLinesInChainedMethodCalls Naming Rule Set BooleanPropertyNaming ClassNaming ConstructorParameterNaming EnumNaming ForbiddenClassName FunctionMaxLength FunctionMinLength FunctionNaming FunctionParameterNaming InvalidPackageDeclaration LambdaParameterNaming MatchingDeclarationName MemberNameEqualsClassName NoNameShadowing NonBooleanPropertyPrefixedWithIs ObjectPropertyNaming PackageNaming TopLevelPropertyNaming VariableMaxLength VariableMinLength VariableNaming Performance Rule Set ArrayPrimitive CouldBeSequence ForEachOnRange SpreadOperator UnnecessaryPartOfBinaryExpression UnnecessaryTemporaryInstantiation Potential-bugs Rule Set AvoidReferentialEquality CastNullableToNonNullableType CastToNullableType Deprecation DontDowncastCollectionTypes DoubleMutabilityForCollection DuplicateCaseInWhenExpression ElseCaseInsteadOfExhaustiveWhen EqualsAlwaysReturnsTrueOrFalse EqualsWithHashCodeExist ExitOutsideMain ExplicitGarbageCollectionCall HasPlatformType IgnoredReturnValue ImplicitDefaultLocale ImplicitUnitReturnType InvalidRange IteratorHasNextCallsNextMethod IteratorNotThrowingNoSuchElementException LateinitUsage MapGetWithNotNullAssertionOperator MissingPackageDeclaration MissingWhenCase NullCheckOnMutableProperty NullableToStringCall PropertyUsedBeforeDeclaration NoConsecutiveBlankLines NoConsecutiveComments NoEmptyClassBody NoEmptyFirstLineInClassBody NoEmptyFirstLineInMethodBlock NoLineBreakAfterElse NoLineBreakBeforeAssignment NoMultipleSpaces NoSemicolons NoSingleLineBlockComment NoTrailingSpaces NoUnitReturn NoUnusedImports NoWildcardImports NullableTypeSpacing PackageName ParameterListSpacing ParameterListWrapping ParameterWrapping PropertyName PropertyWrapping SpacingAroundAngleBrackets SpacingAroundColon SpacingAroundComma SpacingAroundCurly SpacingAroundDot SpacingAroundDoubleColon SpacingAroundKeyword SpacingAroundOperators SpacingAroundParens SpacingAroundRangeOperator SpacingAroundUnaryOperator SpacingBetweenDeclarationsWithAnnotations SpacingBetweenDeclarationsWithComments SpacingBetweenFunctionNameAndOpeningParenthesis StringTemplate StringTemplateIndent TrailingCommaOnCallSite TrailingCommaOnDeclarationSite TryCatchFinallySpacing TypeArgumentListSpacing TypeParameterListSpacing UnnecessaryParenthesesBeforeTrailingLambda Wrapping Libraries Rule Set ForbiddenPublicDataClass LibraryCodeMustSpecifyReturnType LibraryEntitiesShouldNotBePublic RedundantElseInWhen UnconditionalJumpStatementInLoop UnnecessaryNotNullCheck UnnecessaryNotNullOperator UnnecessarySafeCall UnreachableCatchBlock UnreachableCode UnsafeCallOnNullableType UnsafeCast UnusedUnaryOperator UselessPostfixExpression WrongEqualsTypeParameter Ruleauthors Rule Set UseEntityAtName ViolatesTypeResolutionRequirements Style Rule Set AlsoCouldBeApply BracesOnIfStatements BracesOnWhenStatements CanBeNonNullable CascadingCallWrapping ClassOrdering CollapsibleIfStatements DataClassContainsFunctions DataClassShouldBeImmutable DestructuringDeclarationWithTooManyEntries DoubleNegativeLambda EqualsNullCall EqualsOnSignatureLine ExplicitCollectionElementAccessMethod ExplicitItLambdaParameter ExpressionBodySyntax ForbiddenAnnotation ForbiddenComment ForbiddenImport ForbiddenMethodCall ForbiddenSuppress ForbiddenVoid FunctionOnlyReturningConstant LoopWithTooManyJumpStatements MagicNumber MandatoryBracesLoops MaxChainedCallsOnSameLine MaxLineLength MayBeConst ModifierOrder MultilineLambdaItParameter MultilineRawStringIndentation NestedClassesVisibility NewLineAtEndOfFile NoTabs NullableBooleanCheck ObjectLiteralToLambda OptionalAbstractKeyword OptionalUnit OptionalWhenBraces PreferToOverPairSyntax ProtectedMemberInFinalClass RedundantExplicitType RedundantHigherOrderMapUsage RedundantVisibilityModifierRule ReturnCount SafeCast SerialVersionUIDInSerializableClass SpacingBetweenPackageAndImports StringShouldBeRawString ThrowsCount TrailingWhitespace TrimMultilineRawString UnderscoresInNumericLiterals UnnecessaryAbstractClass UnnecessaryAnnotationUseSiteTarget UnnecessaryApply UnnecessaryBackticks UnnecessaryBracesAroundTrailingLambda UnnecessaryFilter UnnecessaryInheritance UnnecessaryInnerClass UnnecessaryLet UnnecessaryParentheses UntilInsteadOfRangeTo UnusedImports UnusedParameter UnusedPrivateClass UnusedPrivateMember UnusedPrivateProperty UseAnyOrNoneInsteadOfFind UseArrayLiteralsInAnnotations UseCheckNotNull UseCheckOrError UseDataClass UseEmptyCounterpart UseIfEmptyOrIfBlank UseIfInsteadOfWhen UseIsNullOrEmpty UseLet UseOrEmpty UseRequire UseRequireNotNull UseSumOfInsteadOfFlatMapSize UselessCallOnNotNull UtilityClassWithPublicConstructor VarCouldBeVal WildcardImport Configuration for Compose FunctionNaming for Compose TopLevelPropertyNaming for Compose LongParameterList for Compose MagicNumber for Compose UnusedPrivateMember for Compose TooManyFunctions for Compose

Slide 21

Slide 21 text

detekt -セットアップ-

Slide 22

Slide 22 text

detekt -セットアップ-

Slide 23

Slide 23 text

detekt -セットアップ- https://github.com/detekt/detekt/discussions/5997

Slide 24

Slide 24 text

detekt -セットアップ-

Slide 25

Slide 25 text

detekt -セットアップ-

Slide 26

Slide 26 text

detekt -カスタムルール-

Slide 27

Slide 27 text

detekt -カスタムルール-

Slide 28

Slide 28 text

PickUp: カスタムルール

Slide 29

Slide 29 text

Jetpack Compose Rules

Slide 30

Slide 30 text

Jetpack Compose Rules State Hoist all the things State should be remembered in composables Use Immutable annotation whenever possible Use mutableStateOf type-specific variants when possible Composables Do not use inherently mutable types as parameters Do not use MutableState as a parameter Be mindful of the arguments you use inside of a restarting effect Do not emit content and return a result Do not emit multiple pieces of content Slots for main content should be the trailing lambda Naming CompositionLocals properly Naming multipreview annotations properly Naming @Composable functions properly Naming Composable annotations properly Ordering @Composable parameters properly Naming parameters properly Movable content should be remembered Make dependencies explicit ViewModels CompositionLocals Preview composables should not be public Modifiers When should I expose modifier parameters? Modifier order matters Modifiers should be used at the top-most layout of the component Don't re-use modifiers Modifiers should have default parameters Naming modifiers properly Avoid Modifier extension factory functions ComponentDefaults ComponentDefaults object should match the composable visibility Opt-in rules Don't use Material 2 Avoid using unstable collections

Slide 31

Slide 31 text

Jetpack Compose Rules コッチ

Slide 32

Slide 32 text

サンプルケースでの 導入を考えてみる

Slide 33

Slide 33 text

ここまでのまとめ ktlint ● コードスタイル・フォーマットの準拠 ● フォーマッタがビルトイン ● 標準で93種類のルールセットを用意 ● IntelliJ Plugin、Gradleほか複数のセット アップ環境を提供 detekt ● コード品質の向上や潜在的なバグの検出 ● フォーマッタは拡張機能として提供 ● 標準で302種類のルールセットを用意 ● IntelliJ Plugin、Gradleほか複数のセット アップ環境を提供

Slide 34

Slide 34 text

想定シチュエーション 以下のスクラッチのフルKotlin開発でのKotlin向けLinterの導入を検討 ● 既存Androidアプリのフルリニューアル開発のエンジニアリード ● 今後も長く走り続けるべく案件化 ● 想定開発期間:1年以上 ● エンジニアは色々な案件を経験するのを推奨(チーム間の移動推奨) ● ハイスキル・ドメインエキスパートなメンバーはチーム間移動の頻度はあまり高くない ● メンバーは常に5名は在籍する状態 ● Android開発経験がないメンバーもアサインされる 🤔

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

頻繁なメンバーの入れ替わり →コーディングスタイル準拠による効果⭕ 今後も長く走り続けるべく案件化 →コード品質、潜在不具合に課題を感じているのでは? ハイスキル・ドメインエキスパートなメンバーはチーム間移動の頻度はあまり高くない → ハイスキルエンジニアの移動を活性化したいのかも? ktlintと併用はどうだろう? →detekt-formattingを試してみてからでもいいかも detektってルール多いし色々指摘されるのは気圧されそう ... →気になるルールはサクッと相談して決めちゃおう 想定シチュエーション 🤔

Slide 37

Slide 37 text

Konsist

Slide 38

Slide 38 text

Konsist ● Architectural Checks / Declarative Checks ● ユニットテスト環境で実行 ○ JUnit4、JUnit5、Kotestなどのテスティングフレームワーク上で実行可能 ● 標準ルールセットはなし

Slide 39

Slide 39 text

Konsist However, there are no industry standards when comes to application architecture. Every code base is different - different class names, different package structures, different application layers, etc. As the project grows code base evolves as well - it tends to have more layers, more modules, and a more complex code structure. These "rules" are hard to capture by generic linter, because they are often specific to the given project. https://docs.konsist.lemonappdev.com/

Slide 40

Slide 40 text

Konsist UseCaseクラスの定義とは? ● ❌GetUserData / ⭕GetUserDataUseCase ● UseCaseがUseCaseを持つことを許容する / しない ● UseCaseは operator fun invoke のみを持つ ● suspend fun / Flow ● UseCaseには@UseCaseをつける ほか 🤔

Slide 41

Slide 41 text

Konsist -セットアップ-

Slide 42

Slide 42 text

Konsist -セットアップ- Typically, it's advisable to consolidate all Konsist tests in a unified location. This approach is preferred because these tests are often designed to validate the structure of the entire project's codebase. There are three potential options for storing Konsist tests: https://docs.konsist.lemonappdev.com/

Slide 43

Slide 43 text

Konsist -セットアップ-

Slide 44

Slide 44 text

Konsist -セットアップ-

Slide 45

Slide 45 text

Konsist -セットアップ- konsistTest内にセットアップできていれば 他のmoduleからkonsistTestへの参照は特には 不要

Slide 46

Slide 46 text

Konsist -セットアップ-

Slide 47

Slide 47 text

Konsist -検査コード-

Slide 48

Slide 48 text

Konsist -検査コード-

Slide 49

Slide 49 text

Konsist -まとめ- ● Architectural Checks / Declarative Checks ● ユニットテスト環境で実行 ● シンプルで強力なAPI ● ファイルレベルの検査であるktlint、detektに対して Konsistはプロダクト全体をスコープにできる ● ktlint、detektでは検査しにくいルールもカバーする柔軟さ ● 汎用的なルールセット(検討中) など

Slide 50

Slide 50 text

まとめ

Slide 51

Slide 51 text

まとめ ktlint ● コードスタイル・フォーマットの準拠向け Linter ● 約90種類の標準ルールセット搭載 ● フォーマッタはビルトイン detekt ● コード品質の向上や潜在的なバグの検出向け Linter ● 標準で302種類のルールセットを用意 ● フォーマッタは拡張機能 Konsist ● アーキテクチャ・宣言を検査可能な Linter ● アーキテクチャ・設計の一貫性の準拠、コード品質の保証 ● 標準ルールセットはないが柔軟な設定が可能

Slide 52

Slide 52 text

Have a nice Kotlin Fest!

Slide 53

Slide 53 text

Appendix ● https://pinterest.github.io/ktlint/latest/ ● https://github.com/pinterest/ktlint ● https://github.com/diffplug/spotless/tree/main/plugin-gradle#quickstart ● https://github.com/diffplug/spotless/issues/615 ● https://detekt.dev/ ● https://detekt.dev/marketplace/ ● https://github.com/detekt/detekt ● https://github.com/detekt/detekt/discussions/5997 ● https://github.com/detekt/detekt-intellij-plugin ● https://github.com/twitter/compose-rules ● https://twitter.github.io/compose-rules/ ● https://mrmans0n.github.io/compose-rules/ ● https://github.com/mrmans0n/compose-rules/tree/main ● https://docs.konsist.lemonappdev.com/ ● https://github.com/DroidKaigi/conference-app-2022/tree/main ● https://github.com/DroidKaigi/conference-app-2023/tree/main ● https://speakerdeck.com/kgmyshin/kotlin-linter ● https://en.wikipedia.org/wiki/Lint_(software)