Introduction and Context Setting

Welcome to our lesson on eliminating duplicated code and enhancing maintainability by extracting functions and refactoring magic numbers. In modern software development, writing clean and maintainable code is crucial, and refactoring is a key practice that helps achieve this. In this lesson, we'll focus on how to identify duplicate code segments and restructure them to improve readability and maintainability.

Throughout this course, we will be using Swift, a powerful and intuitive programming language for iOS, macOS, watchOS, and tvOS app development. We will also utilize XCTest, a robust testing framework, to ensure code reliability and facilitate test-driven development (TDD). These tools help write tests efficiently and manage code evolution seamlessly.

This lesson picks up from an existing ShoppingCart example to focus on the Refactor step of the Red-Green-Refactor cycle. Let's dive into identifying and refactoring duplicated code to improve the codebase.

What Are Code Smells and How Do You Eliminate Them?

Before we delve into specific "smells," it's important to understand the concept of code smells. Code smells refer to indicators in the code that may suggest deeper problems, such as poorly structured code or potential defects, but aren't bugs themselves. Some common examples include duplicate code, long methods, and magic numbers. These signals often hinder code readability and maintainability and can lead to more significant issues over time.

Refactoring patterns provide structured techniques to address these code smells, improving the overall quality of the codebase while avoiding behavioral changes. By employing these patterns, we can systematically transform problematic sections into cleaner, more efficient code. In this lesson, we'll target duplications by leveraging refactoring patterns, such as extracting functions and refactoring magic numbers, to enhance the clarity and adaptability of our application code. We can rely on our tests to ensure that we don't break any existing functionality!

Understanding Code Duplication

The "Code Duplication" smell arises when similar or identical code segments are repeated across a codebase, leading to maintenance difficulties and bugs. For instance, consider constant values spread throughout a codebase without clear labeling. This mirroring phenomenon often complicates updates or adjustments, requiring changes in multiple places and increasing the risk of errors.

Maintaining a DRY (Don't Repeat Yourself) principle ensures each piece of knowledge has a single, unambiguous representation within the system. This leads to better maintainability and understandability, reducing troubleshooting and debugging efforts. Remember, refactoring to eliminate code duplication will fit into our TDD workflow, ensuring minimal disturbance to existing functionalities.

Example: Extracting Functions/Methods

Let's explore how to extract a common logic block as a standalone function using our ShoppingCart class. For this example, assume we initially have repetitive logic calculating the total cost for each item, considering possible discounts. We're starting off with a set of passing tests where there is clear duplication in the implementation.

Refactor - Optimize the Implementation

During refactoring, we will extract a calculateItemCost method into the ShoppingCart class for broader adoption across all functions needing this logic. With the TDD mindset, all functionality remains intact while elevating code quality.

Swift
1class ShoppingCart { 2 var items: [(name: String, price: Double, quantity: Int)] = [] 3 4 func addItem(name: String, price: Double, quantity: Int) { 5 items.append((name: name, price: price, quantity: quantity)) 6 } 7 8 func calculateTotal() -> Double { 9 var total = 0.0 10 11 for item in items { 12 var itemTotal = item.price * Double(item.quantity) 13 14 if item.quantity >= 5 { 15 itemTotal *= 0.9 // 10% discount 16 } 17 18 total += itemTotal 19 } 20 21 return total 22 } 23 24 func calculateTotalWithStudentDiscount() -> Double { 25 var total = 0.0 26 27 for item in items { 28 var itemTotal = item.price * Double(item.quantity) 29 30 if item.quantity >= 5 { 31 itemTotal *= 0.9 // 10% discount 32 } 33 34 total += itemTotal 35 } 36 37 // Apply student discount 38 return total * 0.85 // 15% student discount 39 } 40}

The repetitive logic resides within the calculateTotal and calculateTotalWithStudentDiscount methods. We can extract that into a single function:

Swift
1func calculateItemCost(item: (name: String, price: Double, quantity: Int)) -> Double { 2 var itemTotal = item.price * Double(item.quantity) 3 if item.quantity >= 5 { 4 itemTotal *= 0.9 5 } 6 return itemTotal 7}
Refactor: Extract Another Method

We'll extract a method called calculateSubtotal that can be used by calculateTotal and calculateTotalWithStudentDiscount to eliminate the duplication!

Swift
1class ShoppingCart { 2 var items: [(name: String, price: Double, quantity: Int)] = [] 3 4 func addItem(name: String, price: Double, quantity: Int) { 5 items.append((name: name, price: price, quantity: quantity)) 6 } 7 8 func calculateSubtotal() -> Double { 9 var total = 0.0 10 11 for item in items { 12 total += calculateItemCost(item: item) 13 } 14 15 return total 16 } 17 18 func calculateTotal() -> Double { 19 return calculateSubtotal() 20 } 21 22 func calculateTotalWithStudentDiscount() -> Double { 23 // Apply student discount 24 return calculateSubtotal() * 0.85 // 15% student discount 25 } 26}
Example: Refactoring Magic Numbers

Now let's look at "Magic Numbers," which are the oddly specific numbers that show up in your code. Magic Numbers can cause maintenance headaches when these values change in the future, especially if they are used in more than one place. Here's an example:

Swift
1func calculateItemCost(item: (name: String, price: Double, quantity: Int)) -> Double { 2 var itemTotal = item.price * Double(item.quantity) 3 4 if item.quantity >= 5 { 5 itemTotal *= 0.9 6 } 7 8 return itemTotal 9}
Refactor: Extract Magic Numbers

The solution to magic numbers is simple: hoist them to well-defined constants that can be reused throughout the codebase.

Swift
1let bulkDiscountThreshold = 5 2let bulkDiscountRate = 0.9 // 10% discount 3 4func calculateItemCost(item: (name: String, price: Double, quantity: Int)) -> Double { 5 var itemTotal = item.price * Double(item.quantity) 6 7 if item.quantity >= bulkDiscountThreshold { 8 itemTotal *= bulkDiscountRate 9 } 10 11 return itemTotal 12}
Review, Summary, and Preparation for Practice

In this lesson, we explored the significance of refactoring to eliminate code duplication and magic numbers. By adhering to the TDD cycle — Red (write a failing test), Green (implement with minimal code), and Refactor (improve quality while ensuring behavior remains unchanged) — we ensure our code remains both functional and maintainable.

Now, as you prepare for upcoming practice exercises, focus on applying these skills to further enhance your ability to manage complex codebases. Employ the concepts of DRY and TDD consistently to maintain robust, adaptable code. Keep practicing and mastering these principles, and you’ll soon find yourself developing highly efficient and scalable applications.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal