Code style
Code layout
Know the tools of your IDE, in IntelliJ Idea you should routinely use:
- "Reformat code"
- "Optimize imports"
- "Run code cleanup"
For Kotlin, JSON, etc. the automatic formatting of IntelliJ Idea is preconfigured for the correct code style guides. Use it with Ctrl+Alt+L, or use it automatically with e.g.:
- Tools → Actions on Save → Reformat code / Optimize imports / Run code cleanup
- Git → Commit Checks → Reformat code / Optimize imports / Cleanup
Files
Indentation
For Kotlin, JSON, JavaScript, HTML, etc.: Use 4 spaces per indentation level.
In your IntelliJ IDEA, select "4 spaces" at the bottom of the toolbar (in the classic layout) todo: where to configure in new layout.
Otherwise, when someone else changes the file (and thus the indenting will be automatically set to the default 4 spaces), every single line of the file will show as changed.
If you want to "see" it as more or less, you can configure this in your IDE, but your files have to use the standard 4 spaces, because that is what Git will see.
Source file encoding
Code files have to be encoded with UTF-8, no other locale-specific encodings are allowed. In IntelliJ Idea, you can make sure you're doing it right by looking at the bottom of the toolbar, it should say "UTF-8".
Code
Naming conventions
- Variables:
camelCase - Classes:
PascalCase
Names for call arguments
Whenever a function has arguments that might be unclear, e.g. because of the sheer number of them, use named arguments.
Quick guess, what do the parameters of this snippet refer to?
issueCredential(sdJwtVCDataExampleJson, wallet, true)
IntelliJ has a context action called "Add names to call arguments", so it is always just one press away when hovering over a function or constructor call. This action will automatically add names to the arguments.
If you were using named arguments, would you consider the parameters to be slightly clearer?
issueCredential(issuanceRequest = sdJwtVCDataExampleJson, wallet = wallet, sdJwt = true)
If you read this piece of code, do you know what you are providing to the constructor?
SDMap(requestedList, FIXED, 3)
In the example case:
- the name for the variable could possibly be chosen better,
- the enum value should maybe not be statically imported
- the magic number could be named
However, if we were to provide the name of the arguments (which IntelliJ can automatically do with one click), even the not super clear variables become clear within the context:
SDMap(fields = requestedList, decoyMode = FIXED, decoys = 3)
Write self-documenting code
"Code should explain WHAT it does, comments should explain WHY."
If an implementation requires comments to explain what it does, it is bad code.
Examples
Basic
// Good: Clear intent ✅
fun isValidEmail(email: String) = email.matches(emailRegex)
// Bad ❌
/**
* This function below is so non-descriptive that I actually have to add a comment here that tells you that this
* function is somehow related to checking email.
*/
fun check(input: String) = input.matches(regex)
Processing User Data example
// BAD Implementation - Comments have to be scattered everywhere to explain what the
// code is suppoed to be doing.
class UserProc { // Class for processing users
fun proc(uList: List<String>): List<String> { // Process list of usernames
val r = mutableListOf<String>() // Result list
for (u in uList) { // Loop through each username
if (u.length >= 3) { // Check if username meets minimum length
// Convert to lowercase and remove spaces
val cv = u.lowercase().replace(" ", "")
// Check if username contains only letters
if (cv.all { it.isLetter() }) {
// Add processed username if not already in list
if (!r.contains(cv)) {
r.add(cv)
}
}
}
}
return r // Return processed usernames
}
}
// Good Implementation - Self-documenting
class UserProcessor {
fun sanitizeUsernames(rawUsernames: List<String>): List<String> =
rawUsernames
.filter { username -> username.length >= 3 }
.map { username -> username.lowercase().replace(" ", "") }
.filter { sanitizedUsername -> sanitizedUsername.all { it.isLetter() } }
.distinct()
}
What comments should be used for
"Code should explain WHAT it does, comments should explain WHY."
Explaining Business Logic Rationale
// We're using 2.5% as the risk factor based on historical market data from 2010-2020
// and the Basel III regulatory requirements
private const val RISK_FACTOR = 0.025
// Orders must be processed before 3 PM to meet same-day shipping requirements
// as per contract with ShipCo (Contract #12345)
private const val ORDER_CUTOFF_HOUR = 15
Documenting Complex Algorithms
/**
* Implements the Smith-Waterman algorithm for local sequence alignment.
* Time complexity: O(nm)
* Space complexity: O(nm)
* Reference: Smith, T.F. & Waterman, M.S. (1981). Identification of Common
* Molecular Subsequences. Journal of Molecular Biology 147: 195-197.
*/
fun alignSequences(seq1: String, seq2: String): AlignmentResult {
// Implementation...
}
Warning about Non-Obvious Edge Cases
/**
* Calculates user's premium status.
* Note: This method makes external API calls and should not be called
* from the main thread. Use coroutines or background threading.
*/
suspend fun calculatePremiumStatus(userId: String): PremiumStatus {
// Implementation...
}
Explaining Workarounds for External Issues
// FIXME: Temporary workaround for API version 2.1 bug #45678
// Remove this check once API is upgraded to 2.2
if (apiVersion == "2.1" && response.status == 201) {
return handleSpecialCase(response)
}
// Handling Android API 23 specific behavior
// See: https://developer.android.com/reference/android/os/Build.VERSION_CODES#M
@TargetApi(23)
private fun checkPermissions() {
// Implementation...
}
Documentation for Public APIs
/**
* Processes a payment transaction.
*
* @param amount The transaction amount in cents
* @param currency ISO 4217 currency code
* @throws InsufficientFundsException if the user's balance is too low
* @throws InvalidCurrencyException if the currency is not supported
* @return TransactionResult containing the transaction ID and status
*/
fun processPayment(amount: Long, currency: String): TransactionResult
Explaining Why Something Counter-Intuitive is Necessary
// Don't use nullable type here because null represents a valid business state
// where the user has explicitly opted out of notifications
class UserPreferences(
val notificationEmail: String
)
// We're not using kotlin.math.round() here because we need to handle
// financial rounding according to IEEE 754 specifications
fun roundCurrency(amount: BigDecimal): BigDecimal {
// Implementation...
}
What not to use comments for
Do not write comment spam (redundant comments)
// Bad: Redundant comments
// Function to get user name
fun getUserName(): String {
// Return the user's name
return user.name
}
