Writing clean code with the help of detekt
Hi Techies, In this post We will talk about how can we improve our code with the help of some analysis tool for kotlin. Here we will discuss about detekt analysis tool, why is it important, how we can implement etc.
Detekt will analyze Kotlin code with multiple rule sets and flag the code that breaks any of its rules. Detekt improves the code base by enforcing rule sets including complexity, naming, performance & potential-bugs among others.
let’s discuss why is it important. So whenever in our programming life when we write code then there are lot of chances that we would ignore some important software development rules like “single responsibility principle” string value length, function size and many other. By ignoring these type of rules it may be cause of big blunder in our application or hard to maintain our application. So in sort by analyzing your code with detekt you can increase quality of your kotlin code.
let’s come on second question how can we implement it ? So there are two method to implement one by using SonarQube in which you have to host SonarQube server on your system and then integrate detekt plugin with it that is beneficial in only one case when you want to integrate it with CI/CD.
Second you can integrate it with your android studio project. In this case you can analyze your code by write one command on terminal. Again we can implement it by two approach. First in which our requirement is to implement it on project base that we are going to discuss here and Second in which we can implement it with android plugin. So let’s try to implement first approach step by step.
Step 1
Add detekt plugin in your root level gradle file.
plugins {
id "io.gitlab.arturbosch.detekt" version "1.0.0-RC16"
}
Step 2
Add below code in your root level gradle file to include detekt.gradle file. In which you can define all rules, filters and report file of analyze.
allprojects {
.....
apply from: "$rootDir/your_module/detekt.gradle"
.....
}
Step 3
Create detekt.gradle file and put it in the root directory of your project or module for which you want to analysis code.
apply plugin: 'io.gitlab.arturbosch.detekt'
detekt {
config = files("$rootDir/your_module/detekt_config.yml")
filters = ".*build.*,.*/resources/.*,.*/tmp/.*"
parallel = false // Builds the AST in parallel. Rules are always executed in parallel. Can lead to speedups in larger projects. `false` by default.
ignoreFailures = true // If set to `true` the build does not fail when the maxIssues count was reached. Defaults to `false`.
//Optional baseline, uncomment & run gradle command detektBaseline to exclude existing issues
//baseline = file("$rootDir/detekt-baseline.xml")
reports {
xml {
enabled = true
destination = file("$rootDir/your_module/build/reports/detekt.xml")
}
html {
enabled = true
destination = file("$rootDir/your_module/build/reports/detekt.html")
}
}
}
Step 4
Create detekt_config.yml file and put it in the root directory of your project or your module for which you want to analysis code.
build:
maxIssues: 1
weights:
# complexity: 2
# LongParameterList: 1
# style: 1
# comments: 1
processors:
active: false
exclude:
# - 'FunctionCountProcessor'
# - 'PropertyCountProcessor'
# - 'ClassCountProcessor'
# - 'PackageCountProcessor'
# - 'KtFileCountProcessor'
console-reports:
active: true
exclude:
- 'ProjectStatisticsReport'
- 'ComplexityReport'
- 'NotificationReport'
# - 'FindingsReport'
# - 'BuildFailureReport'
comments:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
CommentOverPrivateFunction:
active: false
CommentOverPrivateProperty:
active: false
EndOfSentenceFormat:
active: false
endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!:]$)
UndocumentedPublicClass:
active: false
searchInNestedClass: true
searchInInnerClass: true
searchInInnerObject: true
searchInInnerInterface: true
UndocumentedPublicFunction:
active: false
complexity:
active: true
ComplexCondition:
active: true
threshold: 4
ComplexInterface:
active: false
threshold: 10
includeStaticDeclarations: false
ComplexMethod:
active: true
threshold: 10
ignoreSingleWhenExpression: false
ignoreSimpleWhenEntries: false
LabeledExpression:
active: false
ignoredLabels: ""
LargeClass:
active: true
threshold: 600
LongMethod:
active: true
threshold: 60
LongParameterList:
active: true
threshold: 6
ignoreDefaultParameters: false
MethodOverloading:
active: false
threshold: 6
NestedBlockDepth:
active: true
threshold: 4
StringLiteralDuplication:
active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
threshold: 3
ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^'
TooManyFunctions:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
thresholdInFiles: 11
thresholdInClasses: 11
thresholdInInterfaces: 11
thresholdInObjects: 11
thresholdInEnums: 11
ignoreDeprecated: false
ignorePrivate: false
ignoreOverridden: false
empty-blocks:
active: true
EmptyCatchBlock:
active: true
allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
EmptyClassBlock:
active: true
EmptyDefaultConstructor:
active: true
EmptyDoWhileBlock:
active: true
EmptyElseBlock:
active: true
EmptyFinallyBlock:
active: true
EmptyForBlock:
active: true
EmptyFunctionBlock:
active: true
ignoreOverriddenFunctions: false
EmptyIfBlock:
active: true
EmptyInitBlock:
active: true
EmptyKtFile:
active: true
EmptySecondaryConstructor:
active: true
EmptyWhenBlock:
active: true
EmptyWhileBlock:
active: true
exceptions:
active: true
ExceptionRaisedInUnexpectedLocation:
active: false
methodNames: 'toString,hashCode,equals,finalize'
InstanceOfCheckForException:
active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
NotImplementedDeclaration:
active: false
PrintStackTrace:
active: false
RethrowCaughtException:
active: false
ReturnFromFinally:
active: false
SwallowedException:
active: false
ignoredExceptionTypes: 'InterruptedException,NumberFormatException,ParseException,MalformedURLException'
ThrowingExceptionFromFinally:
active: false
ThrowingExceptionInMain:
active: false
ThrowingExceptionsWithoutMessageOrCause:
active: false
exceptions: 'IllegalArgumentException,IllegalStateException,IOException'
ThrowingNewInstanceOfSameException:
active: false
TooGenericExceptionCaught:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
exceptionNames:
- ArrayIndexOutOfBoundsException
- Error
- Exception
- IllegalMonitorStateException
- NullPointerException
- IndexOutOfBoundsException
- RuntimeException
- Throwable
allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
TooGenericExceptionThrown:
active: true
exceptionNames:
- Error
- Exception
- Throwable
- RuntimeException
formatting:
active: true
android: false
autoCorrect: true
AnnotationOnSeparateLine:
active: false
ChainWrapping:
active: true
autoCorrect: true
CommentSpacing:
active: true
autoCorrect: true
Filename:
active: true
FinalNewline:
active: true
autoCorrect: true
ImportOrdering:
active: false
Indentation:
active: false
indentSize: 4
continuationIndentSize: 4
MaximumLineLength:
active: true
maxLineLength: 120
ModifierOrdering:
active: true
autoCorrect: true
MultiLineIfElse:
active: true
autoCorrect: true
NoBlankLineBeforeRbrace:
active: true
autoCorrect: true
NoConsecutiveBlankLines:
active: true
autoCorrect: true
NoEmptyClassBody:
active: true
autoCorrect: true
NoItParamInMultilineLambda:
active: false
NoLineBreakAfterElse:
active: true
autoCorrect: true
NoLineBreakBeforeAssignment:
active: true
autoCorrect: true
NoMultipleSpaces:
active: true
autoCorrect: true
NoSemicolons:
active: true
autoCorrect: true
NoTrailingSpaces:
active: true
autoCorrect: true
NoUnitReturn:
active: true
autoCorrect: true
NoUnusedImports:
active: true
autoCorrect: true
NoWildcardImports:
active: true
PackageName:
active: true
autoCorrect: true
ParameterListWrapping:
active: true
autoCorrect: true
indentSize: 4
SpacingAroundColon:
active: true
autoCorrect: true
SpacingAroundComma:
active: true
autoCorrect: true
SpacingAroundCurly:
active: true
autoCorrect: true
SpacingAroundDot:
active: true
autoCorrect: true
SpacingAroundKeyword:
active: true
autoCorrect: true
SpacingAroundOperators:
active: true
autoCorrect: true
SpacingAroundParens:
active: true
autoCorrect: true
SpacingAroundRangeOperator:
active: true
autoCorrect: true
SpacingAroundUnaryOperators:
active: true
autoCorrect: true
StringTemplate:
active: true
autoCorrect: true
naming:
active: true
ClassNaming:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
classPattern: '[A-Z$][a-zA-Z0-9$]*'
ConstructorParameterNaming:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
parameterPattern: '[a-z][A-Za-z0-9]*'
privateParameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
EnumNaming:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
forbiddenName: ''
FunctionMaxLength:
active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
maximumFunctionNameLength: 30
FunctionMinLength:
active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
minimumFunctionNameLength: 3
FunctionNaming:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
excludeClassPattern: '$^'
ignoreOverridden: true
FunctionParameterNaming:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
parameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverriddenFunctions: true
InvalidPackageDeclaration:
active: false
rootPackage: ''
MatchingDeclarationName:
active: true
MemberNameEqualsClassName:
active: false
ignoreOverriddenFunction: true
ObjectPropertyNaming:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
PackageNaming:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$'
TopLevelPropertyNaming:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
constantPattern: '[A-Z][_A-Z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
VariableMaxLength:
active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
maximumVariableNameLength: 64
VariableMinLength:
active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
minimumVariableNameLength: 1
VariableNaming:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
performance:
active: true
ArrayPrimitive:
active: false
ForEachOnRange:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
SpreadOperator:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
UnnecessaryTemporaryInstantiation:
active: true
potential-bugs:
active: true
DuplicateCaseInWhenExpression:
active: true
EqualsAlwaysReturnsTrueOrFalse:
active: false
EqualsWithHashCodeExist:
active: true
ExplicitGarbageCollectionCall:
active: true
InvalidRange:
active: false
IteratorHasNextCallsNextMethod:
active: false
IteratorNotThrowingNoSuchElementException:
active: false
LateinitUsage:
active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
excludeAnnotatedProperties: ""
ignoreOnClassesPattern: ""
MissingWhenCase:
active: false
RedundantElseInWhen:
active: false
UnconditionalJumpStatementInLoop:
active: false
UnreachableCode:
active: true
UnsafeCallOnNullableType:
active: false
UnsafeCast:
active: false
UselessPostfixExpression:
active: false
WrongEqualsTypeParameter:
active: false
style:
active: true
CollapsibleIfStatements:
active: false
DataClassContainsFunctions:
active: false
conversionFunctionPrefix: 'to'
DataClassShouldBeImmutable:
active: false
EqualsNullCall:
active: false
EqualsOnSignatureLine:
active: false
ExplicitItLambdaParameter:
active: false
ExpressionBodySyntax:
active: false
includeLineWrapping: false
ForbiddenComment:
active: true
values: 'TODO:,FIXME:,STOPSHIP:'
ForbiddenImport:
active: false
imports: ''
ForbiddenVoid:
active: false
ignoreOverridden: false
ignoreUsageInGenerics: false
FunctionOnlyReturningConstant:
active: false
ignoreOverridableFunction: true
excludedFunctions: 'describeContents'
LibraryCodeMustSpecifyReturnType:
active: false
LoopWithTooManyJumpStatements:
active: false
maxJumpCount: 1
MagicNumber:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
ignoreNumbers: '-1,0,1,2'
ignoreHashCodeFunction: true
ignorePropertyDeclaration: false
ignoreConstantDeclaration: true
ignoreCompanionObjectPropertyDeclaration: true
ignoreAnnotation: false
ignoreNamedArgument: true
ignoreEnums: false
ignoreRanges: false
MandatoryBracesIfStatements:
active: false
MaxLineLength:
active: true
maxLineLength: 120
excludePackageStatements: true
excludeImportStatements: true
excludeCommentStatements: false
MayBeConst:
active: false
ModifierOrder:
active: true
NestedClassesVisibility:
active: false
NewLineAtEndOfFile:
active: true
NoTabs:
active: false
OptionalAbstractKeyword:
active: true
OptionalUnit:
active: false
OptionalWhenBraces:
active: false
PreferToOverPairSyntax:
active: false
ProtectedMemberInFinalClass:
active: false
RedundantVisibilityModifierRule:
active: false
ReturnCount:
active: true
max: 2
excludedFunctions: "equals"
excludeLabeled: false
excludeReturnFromLambda: true
SafeCast:
active: true
SerialVersionUIDInSerializableClass:
active: false
SpacingBetweenPackageAndImports:
active: false
ThrowsCount:
active: true
max: 2
TrailingWhitespace:
active: false
UnderscoresInNumericLiterals:
active: false
acceptableDecimalLength: 5
UnnecessaryAbstractClass:
active: false
excludeAnnotatedClasses: "dagger.Module"
UnnecessaryApply:
active: false
UnnecessaryInheritance:
active: false
UnnecessaryLet:
active: false
UnnecessaryParentheses:
active: false
UntilInsteadOfRangeTo:
active: false
UnusedImports:
active: false
UnusedPrivateClass:
active: false
UnusedPrivateMember:
active: false
allowedNames: "(_|ignored|expected|serialVersionUID)"
UseCheckOrError:
active: false
UseDataClass:
active: false
excludeAnnotatedClasses: ""
UseRequire:
active: false
UselessCallOnNotNull:
active: false
UtilityClassWithPublicConstructor:
active: false
VarCouldBeVal:
active: false
WildcardImport:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
excludeImports: 'java.util.*,kotlinx.android.synthetic.*'
Now you have successfully implemented detekt in you kotlin project and we just need to run it to analyse our code and get report of all code smells and vulnerability.
if you are running android studio on window system then run command “gradlew detekt” and if you are working on MAC then run “./gradlew detekt” after running this command detekt automatically will generate report file on same location that you specify in step 3 with below parameter
destination = file("$rootDir/your_module/build/reports/detekt.xml")
}
see below screenshot to locate reports:
You can also find example project here . I hope it will be helpful for you. Thank you very much 👍