Навигация в кода мирише: Разкриване на проблеми и създаване на решения в проекти

Миризмите на код са фините индикатори за по-дълбоки проблеми в кодовата база, които, ако не бъдат проверени, могат да доведат до намалена поддръжка, повишена сложност и потенциални грешки.

В проектите на Kotlin разпознаването и справянето с тези миризми на код е от първостепенно значение за производството на чист, ефективен и поддържаем код. Тази статия изследва някои често срещани миризми на код, техните последици и стратегии за коригирането им, за да се подобри качеството на проектите на Kotlin.

Разбиране на това какво е кодови миризми: Кодовите миризми са модели в кода, които намекват за потенциални проблеми с дизайна или изпълнението. Те не се равняват непременно на грешки, а по-скоро показват области, които могат да се възползват от рефакторинг.

Някои преобладаващи кодови миризми включват дълги функции, дублиран код, големи класове, примитивна мания и неподходящи коментари. Идентифицирането на тези миризми изисква бдителност и дълбоко разбиране на принципите на добър софтуерен дизайн.

Дълги функции: Дългите функции, често наричани „функции на Бога“, изпълняват твърде много задачи в рамките на един блок. Това не само пречи на четливостта, но и прави поддръжката трудна задача. Разбиването на такива функции на по-малки, по-фокусирани единици не само подобрява четливостта, но също така подобрява повторното използване и поддръжката.

//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)
}

Дублиран код: Дублираният код е издайнически знак за пропуснати възможности за абстракция и повторно използване на кода. Тези повтарящи се блокове код могат да доведат до несъответствия и грешки, когато са необходими промени. Извличането на обща функционалност в отделни функции или класове насърчава последователността, намалява шансовете за грешки и улеснява усилията за поддръжка.

//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
}

Големи класове: Големите класове, които се справят с множество отговорности, нарушават принципа на единната отговорност. Такива класове стават трудни за разбиране и се променят с течение на времето. Разбиването им на по-малки, фокусирани класове, придържащи се към принципа на единичната отговорност, води до по-модулен и поддържаем код.

//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
    }
}

Примитивно обсебване: Примитивното обсебване включва използването на примитивни типове данни за сложни концепции на домейна, което води до липса на яснота и повишен риск от грешка. Чрез създаване на специални класове за капсулиране на концепции на домейн и тяхното поведение, като например клас Currency, разработчиците подобряват яснотата на кода, безопасността на типа и поддръжката.

// 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")
}

Неподходящи коментари: Неподходящите или прекомерни коментари често показват, че кодът не се обяснява сам по себе си. Вместо да претрупвате кода с коментари, стремежът към самодокументиращ се код със смислени имена на променливи и функции прави кодовата база по-четлива и намалява необходимостта от коментари, които могат да остареят.

//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 ...
}

Дълги списъци с параметри: Функциите с твърде много параметри могат да станат объркващи и това често е знак, че функцията се опитва да направи твърде много.

//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
}

Стратегии за разрешаване на миризми на код: Справянето с миризмите на код изисква стратегически подход:

  1. Прегледи на кода: Редовните прегледи на кода с колеги могат да идентифицират миризмите на кода, които може да бъдат пренебрегнати.
  2. Рефакторинг: Отделете време за рефакторинг, за да премахнете систематично неприятните миризми на кода, като същевременно запазите функционалността.
  3. Инструменти за анализ на код: Използвайте инструменти като Detekt в процеса на изграждане, за да забележите автоматично миризми на код.
  4. Документация и обучение: Обучете екипа си относно миризмите на кода, техните последици и най-добрите практики за избягването им.

Миризмите на код са като указателни табели, насочващи разработчиците към по-поддържаем и ефективен код. Разпознаването на тези миризми и разбирането как да се справят с тях е жизненоважно умение за всеки разработчик.

Чрез активно идентифициране и коригиране на миризми на код, проектите могат да бъдат издигнати до нови нива на четимост, поддръжка и качество. В крайна сметка преследването на чист код е непрекъснато пътуване, при което бдителността и ангажиментът за подобряване водят до софтуер, който не само работи, но и работи добре.