How to Effectively Manage Your Client-Partner Relationships
Clients Project StrategyClients leave when they feel that their needs aren’t being met. The strength of your client-partner relationship relies on a deep understanding of your client’s...
Responding to change is a key tenant of the Agile Manifesto. Whether or not you prescribe to agile, change is undeniably inevitable and predicting that change is hard, if not impossible.
Every software project of sufficient age will undergo change. We cannot predict how it will change, but accepting that change will happen can improve our decision-making process. We have to ship, we have to make money – it’s a business, after all. But resources like time, money, and effort are finite; thus it’s better to be wiser and more efficient in how we spend those resources. There are ways to develop (write) software and architect (plan) code that facilitates responding to change and strengthens stewardship with stakeholders.
This article series explores a coding approach we use at Big Nerd Ranch that enables us to more easily respond to change. It will start by laying out an example and explaining why some “traditional” approaches make change-response difficult. Part 2 will introduce the approach, and Part 3 will complete it.
To help illustrate and understand how we can manage change, let’s look at a simplified Contacts app. I have chosen to use the common Master-Detail style interface, with a TableViewController
showing the master list of Persons. Selecting a Person shows a ViewController
displaying the Person’s details. The implementation is typical and likely familiar:
import UIKit /// My `Person` model. struct Person { let name: String } /// Shows a single Person in detail. class PersonViewController: UIViewController { var person: Person? // Imagine it has functionality to display the Person in detail. } /// Shows a master list of Persons. class PersonsViewController: UITableViewController { let persons = [ Person(name: "Fred"), Person(name: "Barney"), Person(name: "Wilma"), Person(name: "Betty") ] override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let selectedPerson = persons[indexPath.row] // `instantiateFromStoryboard()` is poetic convenience for this example let personVC = PersonViewController.instantiateFromStoryboard() personVC.person = selectedPerson navigationController?.pushViewController(personVC, animated: true) } // Other TableView delegate and data source functions omitted for brevity }
When my app is launched, the PersonsViewController
is the initial ViewController
loaded and displayed. A user can tap on a row/cell of a Person, and the app will navigate to display the details of that Person. This is a fairly common scenario in mobile apps.
As it is now, the app has a single view showing a list of Persons; tap a Person and the Person’s detail is shown. The product stakeholders want to expand the app to support a new feature: Groups. To support this new feature, they want a view showing a list of Persons, and when you tap a person it shows a list of the Person’s Group memberships. The app should change from a single-view UI to a tabbed-UI, with the first tab for Persons feature and the second for the Groups feature. How can we implement this change request in a manner that provides good stewardship of time, money, and resources, and also leads to a more robust, more maintainable code base?
Consider the commonalities: both tabs start by showing a list of Persons. Our PersonsViewController
shows a list of Persons, so we can use it to implement both tabs, right? While it does show persons, it’s not able to satisfy our requirements: the data to display is hard-coded within the PersonsViewController
, and the tight coupling to the PersonViewController
doesn’t support showing Group membership.
How can we solve this?
I’ll immediately disqualify three approaches.
First, duplication. This is creating a new class, such as PersonsGroupViewController
that replicates PersonsViewController
in full (perhaps by select-all, copy, paste) and edits tableView(_:didSelectRowAt:)
to transition to a GroupsViewController
instead of PersonViewController
. Duplicating code in such a manner might work but will become a maintenance nightmare to maintain essentially the same code in multiple places.
Second, subclassing. This is creating a common base class which then PersonsViewController
and PersonsGroupViewController
inherit from, varying just in how the didSelect
is handled. This isn’t necessarily a bad option (and in some cases may be the right approach), but in addition to creating additional maintenance overhead, it’s also not quite the correct model. The display of a list of persons is the same regardless of the action taken when tapping a cell, so to subclass just to change the tap-action is a little much.
Third, to expand PersonsViewController
with some sort of “behavior” or “configuration” enum such as:
class PersonsViewController: UITableViewController { enum SelectionMode { case personDetail case groupMembership } var selectionMode: SelectionMode override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { switch selectionMode { case .personDetail: let personVC = PersonViewController.instantiateFromStoryboard() navigationController?.pushViewController(personVC, animated: true) case .groupMembership: let groupVC = GroupsViewController.instantiateFromStoryboard() navigationController?.pushViewController(groupVC, animated: true) } } } // when filling in tab 1… let personsVC = PersonsViewController.instantiateFromStoryboard() personsVC.selectionMode = .personDetail // when filling in tab 2… let personsVC = PersonsViewController.instantiateFromStoryboard() personsVC.selectionMode = .groupMembership
This approach is problematic because, in addition to making the couplings tighter and more complex, it does not scale well when more modes are added. You might be tempted to think “Oh, it’s just two cases,” but remember we are trying to position the codebase to be to respond to future, and unknown, changes. The reality of codebase maintenance is that, once you establish a pattern, future developers are likely to maintain that pattern. If more changes are required, a future developer is more likely to expand the enum and lead the ViewController
down the road to the ill-fated “Massive View Controller” problem.
There is a better way, which I’ll introduce in part 2.
Clients leave when they feel that their needs aren’t being met. The strength of your client-partner relationship relies on a deep understanding of your client’s...
A lot of this confusion surfaces around the scope of machine learning. While there is a lot of hype around deep learning, it is...
Agile methodologies are now mainstream. But what about the principles of the Agile Manifesto that spawned these methodologies? Here's how Big Nerd Ranch thinks...