Kotlin Documentation
Good documentation is a cornerstone of maintainable code. In this guide, we'll explore how to write clear and effective documentation for your Kotlin projects. Whether you're working alone or in a team, proper documentation makes your code easier to understand, maintain, and extend.
Why Documentation Matters
Documentation serves several important purposes:
- Helps other developers (and your future self) understand your code
- Reduces onboarding time for new team members
- Makes code maintenance easier
- Improves the overall quality of your codebase
KDoc: Kotlin's Documentation Format
Kotlin uses a documentation format called KDoc, which is similar to JavaDoc but tailored for Kotlin's features. KDoc comments begin with /**
and end with */
.
Basic KDoc Structure
/**
* This is a basic KDoc comment.
*
* @param name The name parameter
* @return A greeting message
*/
fun greet(name: String): String {
return "Hello, $name!"
}
When you call this function:
val greeting = greet("Kotlin")
println(greeting) // Outputs: Hello, Kotlin!
Essential KDoc Tags
KDoc supports various tags to provide structured information:
@param
Documents function parameters:
/**
* Calculates the area of a rectangle.
*
* @param width The width of the rectangle
* @param height The height of the rectangle
* @return The area of the rectangle
*/
fun calculateRectangleArea(width: Double, height: Double): Double {
return width * height
}
@return
Documents the return value:
/**
* Checks if the provided number is even.
*
* @param number The number to check
* @return true if the number is even, false otherwise
*/
fun isEven(number: Int): Boolean {
return number % 2 == 0
}
@throws / @exception
Documents exceptions that might be thrown:
/**
* Divides two numbers.
*
* @param dividend The number to be divided
* @param divisor The number to divide by
* @return The result of the division
* @throws ArithmeticException if divisor is zero
*/
fun divide(dividend: Int, divisor: Int): Int {
if (divisor == 0) {
throw ArithmeticException("Cannot divide by zero")
}
return dividend / divisor
}
@see
References related elements:
/**
* Formats a date according to the specified pattern.
*
* @param date The date to format
* @param pattern The pattern to use
* @return The formatted date string
* @see java.text.SimpleDateFormat
*/
fun formatDate(date: Date, pattern: String): String {
val formatter = SimpleDateFormat(pattern)
return formatter.format(date)
}
Documenting Different Kotlin Elements
Classes
/**
* Represents a user in the system.
*
* @property id The unique identifier for the user
* @property name The name of the user
* @property email The email address of the user
* @constructor Creates a new User with the specified details
*/
class User(val id: Int, val name: String, val email: String) {
/**
* Checks if the user has a valid email format.
*
* @return true if the email is valid, false otherwise
*/
fun hasValidEmail(): Boolean {
return email.contains("@") && email.contains(".")
}
}
Properties
class Temperature {
/**
* The temperature in Celsius.
* Valid range is -273.15°C (absolute zero) and above.
*/
var celsius: Double = 0.0
set(value) {
if (value < -273.15) {
throw IllegalArgumentException("Temperature cannot be below absolute zero")
}
field = value
}
/**
* The temperature in Fahrenheit.
* Calculated based on the Celsius value.
*/
val fahrenheit: Double
get() = celsius * 9 / 5 + 32
}
Extension Functions
/**
* Checks if the string is a valid email address format.
*
* @return true if the string is a valid email format, false otherwise
*/
fun String.isValidEmail(): Boolean {
val emailRegex = Regex("[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}")
return matches(emailRegex)
}
Documentation Best Practices
1. Be Clear and Concise
Write documentation that is easy to understand while avoiding unnecessary verbosity:
// Good
/**
* Calculates the sum of two integers.
*
* @param a First integer
* @param b Second integer
* @return The sum of a and b
*/
fun sum(a: Int, b: Int): Int
// Bad - too verbose
/**
* This function takes two integer parameters and returns an integer that is the result
* of adding the first integer parameter to the second integer parameter. The function
* is named sum because it performs addition.
*
* @param a This is the first integer parameter that will be used in the addition operation
* @param b This is the second integer parameter that will be used in the addition operation
* @return Returns an integer representing the sum of the two input parameters
*/
fun sum(a: Int, b: Int): Int
2. Document Non-Obvious Behavior
Focus on explaining behavior that isn't immediately obvious:
/**
* Parses a date string using the specified format.
*
* Note: This function is not thread-safe due to the underlying
* SimpleDateFormat implementation.
*
* @param dateString The date string to parse
* @param format The format pattern
* @return The parsed Date object
* @throws ParseException if the string cannot be parsed
*/
fun parseDate(dateString: String, format: String): Date {
val formatter = SimpleDateFormat(format)
return formatter.parse(dateString)
}
3. Use Code Examples
Include examples for complex functions:
/**
* Filters a list to include only elements that match the predicate.
*
* Example:
* ```
* val numbers = listOf(1, 2, 3, 4, 5)
* val evenNumbers = numbers.filterCustom { it % 2 == 0 }
* // evenNumbers = [2, 4]
* ```
*
* @param predicate The function that determines if an element should be included
* @return A new list containing only the elements that match the predicate
*/
fun <T> List<T>.filterCustom(predicate: (T) -> Boolean): List<T> {
val result = mutableListOf<T>()
for (item in this) {
if (predicate(item)) {
result.add(item)
}
}
return result
}
4. Document APIs Completely
For public APIs, document all public elements:
/**
* Utility class for string operations.
*/
object StringUtils {
/**
* Reverses a string.
*
* @param input The string to reverse
* @return The reversed string
*/
fun reverse(input: String): String {
return input.reversed()
}
/**
* Counts the occurrences of a character in a string.
*
* @param input The string to search in
* @param char The character to count
* @return The number of occurrences
*/
fun countChar(input: String, char: Char): Int {
return input.count { it == char }
}
}
Real-World Example: Documented API Client
Here's a more comprehensive example showing a well-documented HTTP client class:
/**
* A client for making HTTP requests to the Weather API.
*
* This client handles authentication, request formatting, and response parsing
* for interactions with the Weather API.
*
* Example usage:
* ```
* val client = WeatherApiClient("your-api-key")
* val forecast = client.getForecast("London", 5)
* println("5-day forecast for London: ${forecast.summary}")
* ```
*
* @property apiKey The API key used for authentication
* @property baseUrl The base URL for the Weather API
*/
class WeatherApiClient(
private val apiKey: String,
private val baseUrl: String = "https://api.weatherservice.com/v1"
) {
/**
* Retrieves the current weather for the specified location.
*
* @param location The city name or coordinates
* @return The current weather data
* @throws ApiException if the request fails
* @throws AuthenticationException if the API key is invalid
*/
fun getCurrentWeather(location: String): WeatherData {
// Implementation details...
return WeatherData(
temperature = 22.5,
condition = "Sunny",
humidity = 45,
windSpeed = 10.0
)
}
/**
* Retrieves a weather forecast for the specified number of days.
*
* Note: The maximum forecast period is 10 days. Requesting more than
* 10 days will automatically be limited to 10.
*
* @param location The city name or coordinates
* @param days The number of days to forecast (1-10)
* @return The forecast data
* @throws ApiException if the request fails
* @throws IllegalArgumentException if days is less than 1
*/
fun getForecast(location: String, days: Int): ForecastData {
require(days > 0) { "Days must be positive" }
val actualDays = minOf(days, 10)
// Implementation details...
return ForecastData(
location = location,
days = actualDays,
summary = "Mostly sunny with occasional rain",
dailyForecasts = List(actualDays) { day ->
DailyForecast(
day = day + 1,
highTemp = 25.0 - day,
lowTemp = 15.0 - day,
condition = if (day % 3 == 0) "Rainy" else "Sunny"
)
}
)
}
}
/**
* Represents current weather conditions.
*
* @property temperature The current temperature in Celsius
* @property condition The weather condition (e.g., "Sunny", "Cloudy")
* @property humidity The humidity percentage (0-100)
* @property windSpeed The wind speed in km/h
*/
data class WeatherData(
val temperature: Double,
val condition: String,
val humidity: Int,
val windSpeed: Double
)
/**
* Represents a weather forecast for multiple days.
*
* @property location The location for this forecast
* @property days The number of days in the forecast
* @property summary A summary of the overall forecast
* @property dailyForecasts The forecast for each day
*/
data class ForecastData(
val location: String,
val days: Int,
val summary: String,
val dailyForecasts: List<DailyForecast>
)
/**
* Represents the forecast for a single day.
*
* @property day The day number (1 = tomorrow)
* @property highTemp The highest expected temperature in Celsius
* @property lowTemp The lowest expected temperature in Celsius
* @property condition The expected weather condition
*/
data class DailyForecast(
val day: Int,
val highTemp: Double,
val lowTemp: Double,
val condition: String
)
Tools for Generating Documentation
Kotlin documentation can be generated using tools like:
-
Dokka: The official documentation generation tool for Kotlin
bash# Add to build.gradle.kts
plugins {
id("org.jetbrains.dokka") version "1.8.10"
} -
IntelliJ IDEA: Provides built-in support for KDoc with syntax highlighting and code completion
Summary
Good documentation is an essential part of writing maintainable Kotlin code:
- Use KDoc format starting with
/**
and ending with*/
- Document all public API elements including classes, functions, and properties
- Use tags like
@param
,@return
, and@throws
for structured information - Include examples for complex functionality
- Be clear and concise while explaining non-obvious behavior
By following these best practices, you'll create code that's easier to understand, maintain, and collaborate on.
Additional Resources
Exercises
- Add proper KDoc documentation to an existing class in one of your projects
- Generate documentation for a Kotlin project using Dokka
- Review a colleague's code and suggest documentation improvements
- Document a complex algorithm explaining each step in the KDoc comments
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)