Duplication, as the name suggests, is something that exists more than once. The same goes for code duplication. It's code that exists many times in the same codebase. An increase in the codebase, a subsequent rise in code duplication - is this what's happening with you? If yes, then:

Have you ever thought about why this happens?

The code duplication subsequently increases with time as many developers work simultaneously on similar functionalities in the codebase. Is it a good programming practice? No, because coding works on a fundamental principle called DRY, i.e., Don't Repeat Yourself! We must follow the Rule of three, which states:

Two instances of similar code do not require refactoring, but when similar code is used three times, it should be extracted into a new procedure.

Which automated tool can help you find out code duplication?

Copy Paste Detector(CPD) it is!

How do you reduce code duplication?

There are various ways to reduce code duplication, depending on the scenario. Techniques can be as easy as extracting a method and as complex as using generics.

This blog will discuss only the "how" through a few duplication scenarios in the swift application that we ran into and some fundamental programming techniques that helped us reduce them.

Here's the repository consisting of duplicated code, which we will use in the problem statement scenarios below.

  • Scenario 1: We had a custom close UIBarButtonItem on the top left of the navigation bar for a few presented view controllers, each performing the same action of dismissing the controller. Each view controller had the same declaration of the close button and its IBAction definition, resulting in code duplications in every file.

    override func viewDidLoad() {
        super.viewDidLoad()
        let closeButton = UIBarButtonItem(barButtonSystemItem: .close, target: self, action: #selector(closeAction(_:)))
        navigationItem.leftBarButtonItem = closeButton
    }
    
    @IBAction func closeAction(_ sender: UIBarButtonItem) {
        dismiss(animated: true)
    }
    

    Solution: We extracted this method into an extension of UIViewController and used it at all occurrences to add the close bar button item on the top left of the respective view controllers.

    extension UIViewController {
    
        func setCloseButton() {
            let closeButton = UIBarButtonItem(barButtonSystemItem: .close, target: self, action: #selector(closeAction(_:)))
            navigationItem.leftBarButtonItem = closeButton
        }
    
        @IBAction func closeAction(_ sender: UIBarButtonItem) {
            dismiss(animated: true)
        }
    
    }
    

    Use the above extension in any view controller by:

    setCloseButton()
    

    This ensures that the main logic is written only once. This is the real-world scenario where abstraction can be used.

  • Scenario 2: We had an add UIBarButtonItem on the top right of the navigation bar for all the view controllers, each of which performed the same action of opening the Add Page.

    func setAddButtonUI() {
        addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addButtonAction(_:)))
        navigationItem.rightBarButtonItem = addButton
    }
    
    @IBAction func addButtonAction(_ sender: UIBarButtonItem) {
        guard let thirdVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ThirdViewController") as? ThirdViewController else {
            return
        }
        present(UINavigationController(rootViewController: thirdVC), animated: true)
    }
    

    Solution: As we required this UIBarButtonItem on each view controller, we created a parent view controller and inherited the child view controllers from this view controller.

    class ParentViewController: UIViewController {
    
        var addButton = UIBarButtonItem()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            setAddButtonUI()
        }
    
        func setAddButtonUI() {
            addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addButtonAction(_:)))
            navigationItem.rightBarButtonItem = addButton
        }
    
        @IBAction func addButtonAction(_ sender: UIBarButtonItem) {
            //Present AddViewController
        }
    
    }
    

    Use the ParentViewController like this for all view controllers that need the add button on the top right:

    class FirstViewController: ParentViewController {
    

    instead of

    class FirstViewController: UIViewController {
    

    This is how inheritance can refactor code and reduce code duplication.

  • Scenario 3: We wanted to use the inbuilt UIAlertView along with different titles, descriptions, actions, and their handlers, on almost every view controller. Thus the main code to display this was coded for each view controller separately. Here are two UIAlertController code snippets from different view controllers:

    let alertController = UIAlertController(title: "First", message: "First Description", preferredStyle: .alert)
    alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
    present(alertController, animated: true)
    
    let alertController = UIAlertController(title: "Second", message: "Second Description", preferredStyle: .alert)
    alertController.addAction(UIAlertAction(title: "Yes", style: .default, handler: { _ in
        print("Yes clicked")
    }))
    present(alertController, animated: true)
    

    Solution: For this, we created a custom class, say AlertUtility, that had all the common customizable code:

    class AlertUtility {
    
        func showAlertPopup(_ alertType: CustomAlertType, title: String = "OK", handler: ((UIAlertAction) -> Void)? = nil) {
            let alertController = UIAlertController(title: alertType.title, message: alertType.message, preferredStyle: .alert)
            alertController.addAction(UIAlertAction(title: title, style: .default, handler: handler))
            topViewController()?.present(alertController, animated: true)
        }
    
        func topViewController() -> UIViewController? {
            let rootViewController = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.rootViewController
            if let presentedViewController = rootViewController?.presentedViewController {
                return presentedViewController
            }
            return rootViewController
        }
    
    }
    

    Use it in various ways like:

    AlertUtility().showAlertPopup(.first)
    
    AlertUtility().showAlertPopup(.second, title: "Yes") { _ in
          print("Yes clicked")
    }
    

    This is a way to utilize a function in various forms, fundamentally called polymorphism.

    We also used the following enum to simplify the alert title and corresponding descriptions:

    enum CustomAlertType {
    
        case first
        case second
        case more
    
        public var title: String {
            var value: String
            switch self {
            case .first: value = "First"
            case .second: value = "Second"
            case .more: value = "More"
            }
            return value
        }
    
        public var message: String {
            var value: String
            switch self {
            case .first: value = "First Description"
            case .second: value = "Second Description"
            case .more: value = "More Description"
            }
            return value
        }
    
    }
    

Here is the code repository containing the examples after the removal of duplications. Swift 5.6.1 and Xcode 13.4.1 were used to create this.

Needless to say, code duplication impacts software quality, not only lines of code but also maintenance. As a result, the lower the code duplication, the lower the cost of code development and maintenance. We hope this blog helped you understand how to reduce code duplications.

References: