YE MON KYAW
Arpalar Tech
Published in
6 min readAug 30, 2023

--

Navigating Code Smells: Unveiling Issues and Crafting Solutions in Projects

Code smells are the subtle indicators of deeper issues within a codebase that, if left unchecked, can lead to decreased maintainability, increased complexity, and potential bugs.

In Kotlin projects, recognizing and addressing these code smells is paramount for producing clean, efficient, and maintainable code. This article explores some common code smells, their implications, and strategies for rectifying them to enhance the quality of Kotlin projects.

Understanding What is Code Smells: Code smells are patterns in code that hint at potential design or implementation problems. They don’t necessarily equate to bugs but rather indicate areas that might benefit from refactoring.

Some prevalent code smells include long functions, duplicated code, large classes, primitive obsession, and inappropriate comments. Identifying these smells requires vigilance and a keen understanding of good software design principles.

Long Functions: Long functions, often dubbed the “God functions,” perform too many tasks within a single block. This not only hampers readability but also makes maintenance a daunting task. Breaking down such functions into smaller, more focused units not only improves readability but also enhances reusability and maintainability.

//Incorrect Example - Long Function for Wine Order: Code Smell
fun placeWineOrder(customer: Customer, items: List<WineItem>) {
// Validate customer
if (customer.name.isEmpty() || customer.age < 18) {
throw IllegalArgumentException("Invalid customer.")
}

// Calculate total price
var totalPrice = 0.0
for (item in items) {
if (item.quantity <= 0) {
throw IllegalArgumentException("Invalid item quantity.")
}
totalPrice += item.price * item.quantity
}

// Apply discount
if (totalPrice > 200) {
totalPrice *= 0.9
}

// Generate order details
val orderDetails = StringBuilder()
orderDetails.append("Order for ${customer.name}\n")
for (item in items) {
orderDetails.append("${item.quantity} x ${item.name}: $${item.price * item.quantity}\n")
}
orderDetails.append("Total price: $totalPrice")

// Send order confirmation
EmailService.sendEmail(customer.email, "Order Confirmation", orderDetails.toString())

// Update inventory
for (item in items) {
Inventory.updateQuantity(item, -item.quantity)
}

// Update customer points
customer.points += (totalPrice / 10).toInt()
Database.updateCustomer(customer)
}

//Correct Refactor - Breaking Down the Function for Wine Order
fun placeWineOrder(customer: Customer, items: List<WineItem>) {
validateCustomer(customer)
val totalPrice = calculateTotalPrice(items)
applyDiscount(totalPrice)
val orderDetails = generateOrderDetails(customer, items, totalPrice)
sendOrderConfirmation(customer, orderDetails)
updateInventory(items)
updateCustomerPoints(customer, totalPrice)
}

fun validateCustomer(customer: Customer) {
if (customer.name.isEmpty() || customer.age < 18) {
throw IllegalArgumentException("Invalid customer.")
}
}

fun calculateTotalPrice(items: List<WineItem>): Double {
return items.sumByDouble { it.price * it.quantity }
}

fun applyDiscount(totalPrice: Double) {
if (totalPrice > 200) {
totalPrice *= 0.9
}
}

fun generateOrderDetails(customer: Customer, items: List<WineItem>, totalPrice: Double): String {
val orderDetails = StringBuilder()
orderDetails.append("Order for ${customer.name}\n")
for (item in items) {
orderDetails.append("${item.quantity} x ${item.name}: $${item.price * item.quantity}\n")
}
orderDetails.append("Total price: $totalPrice")
return orderDetails.toString()
}

fun sendOrderConfirmation(customer: Customer, orderDetails: String) {
EmailService.sendEmail(customer.email, "Order Confirmation", orderDetails)
}

fun updateInventory(items: List<WineItem>) {
for (item in items) {
Inventory.updateQuantity(item, -item.quantity)
}
}

fun updateCustomerPoints(customer: Customer, totalPrice: Double) {
customer.points += (totalPrice / 10).toInt()
Database.updateCustomer(customer)
}

Duplicated Code: Duplicated code is a telltale sign of missed opportunities for abstraction and code reuse. These repeated blocks of code can lead to inconsistencies and errors when changes are required. Extracting common functionality into separate functions or classes promotes consistency, reduces the chances of errors, and eases maintenance efforts.

//Code Smell
fun calculateCircleArea(radius: Double): Double {
return 3.14 * radius * radius
}

fun calculateRectangleArea(width: Double, height: Double): Double {
return 3.14 * width * height // Oops, copy-paste mistake
}
//Correct Refactor
fun calculateCircleArea(radius: Double): Double {
return 3.14 * radius * radius
}

fun calculateRectangleArea(width: Double, height: Double): Double {
return width * height
}

Large Classes: Large classes that handle numerous responsibilities violate the single responsibility principle. Such classes become difficult to understand and modify over time. Breaking them down into smaller, focused classes adhering to the single responsibility principle results in more modular and maintainable code.

//Long Class - WineManager 
class WineManager {
fun addWine(wine: Wine) {
// Add wine to database
}

fun updateWine(wine: Wine) {
// Update wine details in database
}

fun calculateTotalRevenue() {
// Calculate total revenue from all wines
}

fun processOrder(order: Order) {
// Validate order, deduct quantities, update inventory, and send confirmation
}

fun generateSalesReport() {
// Generate sales report with various statistics
}

// ... more methods related to wine management ...
}
//Refactored - Smaller, Focused Classes:
class WineCatalog {
fun addWine(wine: Wine) {
// Add wine to database
}

fun updateWine(wine: Wine) {
// Update wine details in database
}
}

class RevenueCalculator {
fun calculateTotalRevenue() {
// Calculate total revenue from all wines
}
}

class OrderProcessor {
fun processOrder(order: Order) {
// Validate order, deduct quantities, update inventory, and send confirmation
}
}

class SalesReportGenerator {
fun generateSalesReport() {
// Generate sales report with various statistics
}
}

Primitive Obsession: Primitive Obsession involves using primitive data types for complex domain concepts, leading to a lack of clarity and increased error risk. By creating dedicated classes to encapsulate domain concepts and their behavior, such as a Currency class, developers enhance code clarity, type safety, and maintainability.

// Wrong: Using primitive types for currencies
val usdAmount: Double = 100.0
val eurAmount: Double = 80.0
val exchangeRateToUSD: Double = 1.18

// Performing calculations without type safety or clarity
val totalAmount: Double = usdAmount + (eurAmount * exchangeRateToUSD)
println("Total amount in USD: $totalAmount")
// Correct: Using a dedicated Currency class
class Currency(val code: String, val exchangeRateToUSD: Double) {
fun convertToUSD(amount: Double): Double {
return amount * exchangeRateToUSD
}
}

fun main() {
val usd = Currency("USD", 1.0)
val eur = Currency("EUR", 1.18)
val amountInEur = 80.0

// Using the Currency class for conversions
val amountInUSD = eur.convertToUSD(amountInEur)
println("Amount in USD: $amountInUSD")
}

Inappropriate Comments: Inappropriate or excessive comments often indicate that the code is not self-explanatory. Rather than cluttering the code with comments, striving for self-documenting code with meaningful variable and function names makes the codebase more readable and reduces the need for comments that can become outdated.

//Code Smell - Inappropriate Comments:
class WineManager {
// This is the WineManager class
// It manages various wine-related functionalities
// Constructor for creating a new WineManager instance
constructor() {
// Initialize the WineManager instance
}

// Method to add a wine to the catalog
fun addWine(wine: Wine) {
// Code to add the wine to the catalog
}

// Method to update the wine details
// Takes a Wine object as parameter
fun updateWine(wine: Wine) {
// Code to update the wine details
}

// ... other methods with similar comments ...
}
//Refactored - Clearer Code, Concise Comments:
class WineManager {
constructor() {
// Initialize the WineManager
}

fun addWine(wine: Wine) {
// Add the wine to the catalog
}

fun updateWine(wine: Wine) {
// Update the wine details
}

// ... other methods without redundant comments ...
}

Long Parameter Lists: Functions with too many parameters can become confusing, and it’s often a sign that the function is trying to do too much.

//Code Smell : Long Parameter
fun processWineOrder(
customerId: Int,
customerName: String,
customerEmail: String,
wines: List<Wine>,
quantities: List<Int>,
isPreferredCustomer: Boolean,
shippingAddress: String,
billingAddress: String
) {
// Process the order using the provided parameters
}
//Refactored - Introducing a Data Class:
data class WineOrder(
val customer: Customer,
val items: List<OrderItem>,
val shippingAddress: String,
val billingAddress: String
)

data class Customer(
val id: Int,
val name: String,
val email: String,
val isPreferred: Boolean
)

data class OrderItem(
val wine: Wine,
val quantity: Int
)

fun processWineOrder(order: WineOrder) {
// Process the order using the encapsulated details
}

Strategies for Solving Code Smells: Addressing code smells requires a strategic approach:

  1. Code Reviews: Regular code reviews with colleagues can identify code smells that might be overlooked.
  2. Refactoring: Dedicate time for refactoring to systematically eliminate code smells while maintaining functionality.
  3. Code Analysis Tools: Leverage tools like Detekt in your build process to automatically spot code smells.
  4. Documentation and Training: Educate your team about code smells, their implications, and best practices to avoid them.

Code smells are like signposts guiding developers toward more maintainable and efficient code. Recognizing these smells and understanding how to address them is a vital skill for any developer.

By actively identifying and rectifying code smells, projects can be elevated to new levels of readability, maintainability, and quality. Ultimately, the pursuit of clean code is an ongoing journey, where vigilance and a commitment to improvement lead to software that not only works but works well.

--

--

YE MON KYAW
Arpalar Tech

Software Engineer who write code in Kotlin / Android