Welcome to the very first lesson of the "Applying Clean Code Principles" course! In this lesson, we will focus on a fundamental concept in clean coding: the DRY ("Don't Repeat Yourself") principle. Understanding DRY is crucial for writing efficient, maintainable, and clean code. This principle is not just important for coding interviews but also in everyday software development. Today, we will dive deep into issues caused by repetitive code and explore strategies to combat redundancy. 🚀
Repetitive functionality in code can introduce several issues that affect the efficiency and maintainability of your software:
-
Code Bloat: Repeating similar code across different parts of your application unnecessarily increases the size of the codebase. This makes the code harder to navigate and increases the chances of introducing errors.
-
Risk of Inconsistencies: When similar pieces of logic are scattered across different areas, it's easy for them to become out of sync during updates or bug fixes. This can result in logic discrepancies and potentially introduce new problems.
-
Maintenance Challenges: Updating code often requires modifications in multiple places, leading to increased work and a higher likelihood of errors. Redundant code makes it difficult for developers to ensure all necessary changes have been made consistently.
To adhere to the DRY principle and avoid repeating yourself, several strategies can be employed:
-
Extracting Method: Move repeated logic into a dedicated method that can be called wherever needed. This promotes reuse and simplifies updates.
-
Extracting Variable: Consolidate repeated expressions or values into variables. This centralizes change, reducing the potential for errors.
-
Replace Temp with Query: Use a method to compute values on demand rather than storing them in temporary variables, aiding in readability and reducing redundancy.
Consider the following problematic code snippet where repetitive logic is used for calculating the total price based on different shipping methods:
Ruby1def calculate_click_and_collect_total(order) 2 items_total = 0 3 # Iteration to calculate items_total 4 order.items.each do |item| 5 items_total += item.price * item.quantity 6 end 7 shipping_cost = items_total > 100 ? 0 : 5 8 items_total + shipping_cost + order.tax 9end 10 11def calculate_post_shipment_total(order, is_express) 12 items_total = 0 13 # Duplicate: Iteration to calculate items_total 14 order.items.each do |item| 15 items_total += item.price * item.quantity 16 end 17 shipping_cost = is_express ? items_total * 0.1 : items_total * 0.05 18 items_total + shipping_cost + order.tax 19end
Both methods contain duplicated logic for calculating the total price of items, making them error-prone and hard to maintain. Now, let's refactor this code.
By consolidating the shared logic into a separate method, we can eliminate redundancy and streamline updates:
Ruby1def calculate_click_and_collect_total(order) 2 # Calculate items total (duplicated logic) 3 items_total = calculate_items_total(order) 4 shipping_cost = items_total > 100 ? 0 : 5 5 items_total + shipping_cost + order.tax 6end 7 8def calculate_post_shipment_total(order, is_express) 9 # Calculate items total (duplicated logic) 10 items_total = calculate_items_total(order) 11 shipping_cost = is_express ? items_total * 0.1 : items_total * 0.05 12 items_total + shipping_cost + order.tax 13end 14 15def calculate_items_total(order) 16 # Extracted method for items total calculation 17 items_total = 0 18 order.items.each do |item| 19 items_total += item.price * item.quantity 20 end 21 items_total 22end
By extracting the calculate_items_total
method, we centralize the logic of item total calculation, leading to cleaner, more maintainable code.
Let's look at another example dealing with repeated calculations for discount rates:
Ruby1def apply_discount(price, customer) 2 loyalty_discount = customer.loyalty_level * 0.02 3 price *= (1 - loyalty_discount) 4 # Additional discounts 5 seasonal_discount = 0.10 6 price *= (1 - seasonal_discount) 7 price 8end
Here, the discount rates are scattered throughout the code, which complicates management and updates.
We can simplify this by extracting the discount rates into variables:
Ruby1def apply_discount(price, customer) 2 loyalty_discount = customer.loyalty_level * 0.02 3 seasonal_discount = 0.10 4 5 total_discount = 1 - loyalty_discount - seasonal_discount 6 price *= total_discount 7 8 price 9end
Now, with the total_discount
variable, the logic is cleaner, more readable, and allows changes in just one place. 🎉
Our final example involves temporary variables that lead to repetition:
Ruby1def eligible_for_discount?(customer) 2 # Logic to determine new_customer 3 new_customer = customer.sign_up_date > Date.today << 3 4 new_customer && customer.purchase_history.size > 5 5end 6 7def eligible_for_loyalty_program?(customer) 8 # Duplicate: Logic to determine new_customer 9 new_customer = customer.sign_up_date > Date.today << 3 10 new_customer || customer.loyalty_level > 3 11end
The variable new_customer
is used in multiple places, causing duplicated logic.
Let's refactor by extracting the logic into a method, reducing duplication and enhancing modularity:
Ruby1def eligible_for_discount?(customer) 2 new_customer?(customer) && customer.purchase_history.size > 5 3end 4 5def eligible_for_loyalty_program?(customer) 6 new_customer?(customer) || customer.loyalty_level > 3 7end 8 9def new_customer?(customer) 10 customer.sign_up_date > Date.today << 3 11end
By creating the new_customer?
method, we've simplified the code and made it more maintainable. 🚀
In this lesson, you learned about the DRY principle and strategies like Extracting Method, Extracting Variable, and Replace Temp with Query to eliminate code redundancy. These strategies help to create code that is easier to maintain, enhance, and understand. Next, you'll have the opportunity to apply these concepts in practical exercises, strengthening your ability to refactor code and uphold clean coding standards. Happy coding! 😊