Keep your code clean with Compose Method

“Transform the logic into a small number of intention-revealing steps at the same level of detail.”

Compose Method

Today we’ll learn something new about keeping the code clean and easy to understand for us and the other fellow humans. A few days ago, I found on my desk an intriguing book called “Refactoring to patterns” by Joshua Kerievsky. Given that I was not necessarily a person who reads a lot, I thought that I should change this. I started reading the book with an open heart, thinking that it will help me and the people I’m working with to create better and easier to maintain code. So far, the book has a lot of interesting topics covered, in regards to refactoring code into better and cleaner patterns.

One of the simplest refactoring shows how we can keep our methods small and well written. To achieve this we’ll apply a technique called “Compose Method”. Although for me the name didn’t say a lot when I read the title, right after I saw the example it was clear enough what it meant. When a method body is too big, extract the code into smaller nicely named methods. As Uncle Bob puts it “Extract till you drop”. 😂

Crowded viewDidLoad

To do so, I’m going to show you a small piece of code that illustrates the concept. The code comes from one of my recently made application called Curtain for Parents. Let’s dig into it:

override func viewDidLoad() {
super.viewDidLoad()
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Category")
tableView.backgroundColor = UIColor.backgroundColor()
title = "Categories"
tableView.cellLayoutMarginsFollowReadableWidth = false
tableView.registerNib(UINib(nibName: "VTSettingsCell", bundle: nil), forCellReuseIdentifier: self.cellIdentifier)
tableView.registerNib(UINib(nibName: "VTTextFieldTableViewCell", bundle: nil), forCellReuseIdentifier: self.textfieldCellIdentifier)
loadCategories()
CategoriesService.sharedInstance.addObserver(self)
let button = UIBarButtonItem(image: UIImage(named: "add")?.imageWithRenderingMode(.AlwaysOriginal), style: UIBarButtonItemStyle.Plain, target: self, action: #selector(VTCategoriesViewController.addCategory))
addNewCategoryButton = button
navigationItem.rightBarButtonItem = button
let back = UIBarButtonItem(image: UIImage(named: "back")?.imageWithRenderingMode(.AlwaysOriginal), style: UIBarButtonItemStyle.Plain, target: self, action: #selector(VTCategoriesViewController.back))
navigationItem.leftBarButtonItem = back
}

This implementation of viewDidLoad() does quite a few things. First we configure the tableView, then we prepare our dataSource, and finally we’re configuring our navigation bar buttons. For me this looks like a common place to do all of this. While this is acceptable, I think we can make this read like a well written prose. 🤓

Step 1: Separate Table View logic

First thing I’ll do is separate the tableView configuration into its own method. Create a method called configureTableView() and move configuration logic for the table view inside this method:

func configureTableView() {
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Category")
tableView.backgroundColor = UIColor.backgroundColor()
tableView.cellLayoutMarginsFollowReadableWidth = false
tableView.registerNib(UINib(nibName: "VTSettingsCell", bundle: nil), forCellReuseIdentifier: self.cellIdentifier)
tableView.registerNib(UINib(nibName: "VTTextFieldTableViewCell", bundle: nil), forCellReuseIdentifier: self.textfieldCellIdentifier)
}

Step 2: Separate Navigation Bar logic

Then let’s move the code which handles the logic for creating the navigation bar button into its own method called configureNavigationBarButtons():

func configureNavigationBarButtons() {
let button = UIBarButtonItem(image: UIImage(named: "add")?.imageWithRenderingMode(.AlwaysOriginal), style: UIBarButtonItemStyle.Plain, target: self, action: #selector(VTCategoriesViewController.addCategory))
self.addNewCategoryButton = button
self.navigationItem.rightBarButtonItem = button

let back = UIBarButtonItem(image: UIImage(named: "back")?.imageWithRenderingMode(.AlwaysOriginal), style: UIBarButtonItemStyle.Plain, target: self, action: #selector(VTCategoriesViewController.back))
self.navigationItem.leftBarButtonItem = back
}

Step 3: Wrapping up

After doing so, we need to make sure to call both methods from viewDidLoad() implementation:

override func viewDidLoad() {
super.viewDidLoad()
configureTableView()
configureNavigationBarButton()
CategoriesService.sharedInstance.addObserver(self)
self.title = "Categories"
loadCategories()
}

This definitely looks way better and it is easier to understand its functionality. You might actually read it as: When view loads, configure table view and navigation buttons, observe data changes, set title and load categories for display. If you want to go deeper you can extract even more, but for our purpose this is enough.

The approach is quite simple. A good way to refactor large methods is to move blocks of code into well named methods. 😇

References

https://www.amazon.com/Refactoring-Patterns-Joshua-Kerievsky/dp/0321213351
 
https://cleancoders.com
 https://www.industriallogic.com/xp/refactoring/composeMethod.html


Originally published at qbits.