Top Banner
Templates Considered Harmful Apple Templates and the Single Responsibility Principle Brian Gesiak March 28th, 2014 at Recruit Co., Ltd. Research Student, The University of Tokyo @modocache
97
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Apple Templates Considered Harmful

Templates Considered HarmfulApple Templates and the Single Responsibility Principle

Brian Gesiak

March 28th, 2014 at Recruit Co., Ltd.

Research Student, The University of Tokyo

@modocache

Page 2: Apple Templates Considered Harmful

Today

• The single responsibility principle (SRP) • The UITableViewController file template

• Model and controller code all rolled into one • Example of how to separate concerns

• Moving data store logic out of the controller in GitHubViewer.app

• Apple file and project templates • Many violate the single responsibility principle • Best to think of them as “proof of concepts”

Page 3: Apple Templates Considered Harmful

Single Responsibility Principle

A class should have one, and only one, reason to change.

- Robert C. Martin

Page 4: Apple Templates Considered Harmful

Robert C. Martin (Uncle Bob)

Page 5: Apple Templates Considered Harmful

UITableViewController

#pragma mark - UIViewController - (id)initWithStyle:(UITableViewStyle)style { /* ... */ } - (void)viewDidLoad { /* ... */ } - (void)didReceiveMemoryWarning { /* ... */ } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { /* ... */ } !#pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView: (UITableView *)tableView { /* ... */ } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { /* ... */ } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ } !#pragma mark - UITableViewDelegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ }

What’s Included in the Template

Page 6: Apple Templates Considered Harmful

UITableViewController

#pragma mark - UIViewController - (id)initWithStyle:(UITableViewStyle)style { /* ... */ } - (void)viewDidLoad { /* ... */ } - (void)didReceiveMemoryWarning { /* ... */ } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { /* ... */ } !#pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView: (UITableView *)tableView { /* ... */ } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { /* ... */ } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ } !#pragma mark - UITableViewDelegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ }

What’s Included in the Template

Page 7: Apple Templates Considered Harmful

UITableViewController

#pragma mark - UIViewController - (id)initWithStyle:(UITableViewStyle)style { /* ... */ } - (void)viewDidLoad { /* ... */ } - (void)didReceiveMemoryWarning { /* ... */ } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { /* ... */ } !#pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView: (UITableView *)tableView { /* ... */ } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { /* ... */ } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ } !#pragma mark - UITableViewDelegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ }

What’s Included in the Template

Page 8: Apple Templates Considered Harmful

UITableViewController

#pragma mark - UIViewController - (id)initWithStyle:(UITableViewStyle)style { /* ... */ } - (void)viewDidLoad { /* ... */ } - (void)didReceiveMemoryWarning { /* ... */ } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { /* ... */ } !#pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView: (UITableView *)tableView { /* ... */ } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { /* ... */ } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ } !#pragma mark - UITableViewDelegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ }

What’s Included in the Template

Page 9: Apple Templates Considered Harmful

UITableViewDelegate

UITableViewDataSource

UITableViewControllerResponsibilities, a.k.a. “Reasons to Change”

1. Determines what data in displayed in the table !

!

2. Determines how users interact with the table

Page 10: Apple Templates Considered Harmful

UITableViewDelegate

UITableViewDataSource

UITableViewControllerResponsibilities, a.k.a. “Reasons to Change”

1. Determines what data in displayed in the table !

!

2. Determines how users interact with the table

Page 11: Apple Templates Considered Harmful

UITableViewDelegate

UITableViewDataSource

UITableViewControllerResponsibilities, a.k.a. “Reasons to Change”

1. Determines what data in displayed in the table !

!

2. Determines how users interact with the table

Page 12: Apple Templates Considered Harmful

UITableViewDelegate

UITableViewDataSource

UITableViewControllerResponsibilities, a.k.a. “Reasons to Change”

1. Determines what data in displayed in the table !

!

2. Determines how users interact with the table

Page 13: Apple Templates Considered Harmful

GitHubViewer

GitHub icon by @peterhajas, licensed under the Creative Commons License version 3.0.

Page 14: Apple Templates Considered Harmful

GitHubViewer

GitHub icon by @peterhajas, licensed under the Creative Commons License version 3.0.

Page 15: Apple Templates Considered Harmful

GHVReposViewController

- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; }

View Controller Responsibilities

Page 16: Apple Templates Considered Harmful

GHVReposViewController

- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; }

View Controller Responsibilities

Page 17: Apple Templates Considered Harmful

GHVReposViewController

- (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; }

View Controller Responsibilities

Page 18: Apple Templates Considered Harmful

GHVReposViewController

- (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; }

View Controller Responsibilities

Page 19: Apple Templates Considered Harmful

GHVReposViewController

- (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; }

View Controller Responsibilities

Page 20: Apple Templates Considered Harmful

GHVReposViewController

- (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; }

View Controller Responsibilities

Page 21: Apple Templates Considered Harmful

GHVReposViewController

- (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; }

View Controller Responsibilities

Page 22: Apple Templates Considered Harmful

GHVReposViewController

- (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; }

View Controller Responsibilities

Page 23: Apple Templates Considered Harmful

GHVReposViewController

- (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; }

View Controller Responsibilities

Page 24: Apple Templates Considered Harmful

GHVReposViewController

- (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; }

View Controller Responsibilities

Page 25: Apple Templates Considered Harmful

GHVReposViewController

- (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; }

View Controller Responsibilities

Page 26: Apple Templates Considered Harmful

GHVReposViewController

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } !- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } !- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; }

UITableViewDataSource Responsibilities

Page 27: Apple Templates Considered Harmful

GHVReposViewController

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } !- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } !- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; }

UITableViewDataSource Responsibilities

Page 28: Apple Templates Considered Harmful

GHVReposViewController

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } !- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } !- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; }

UITableViewDataSource Responsibilities

Page 29: Apple Templates Considered Harmful

GHVReposViewController

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } !- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } !- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; }

UITableViewDataSource Responsibilities

Page 30: Apple Templates Considered Harmful

GHVReposViewController

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } !- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } !- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; }

UITableViewDataSource Responsibilities

Page 31: Apple Templates Considered Harmful

GHVReposViewController

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } !- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } !- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; }

UITableViewDataSource Responsibilities

Page 32: Apple Templates Considered Harmful

GHVReposViewController

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } !- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } !- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; }

UITableViewDataSource Responsibilities

Page 33: Apple Templates Considered Harmful

GHVReposViewController

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } !- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } !- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; }

UITableViewDataSource Responsibilities

Page 34: Apple Templates Considered Harmful

GHVReposViewController

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } !- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } !- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; }

UITableViewDataSource Responsibilities

Page 35: Apple Templates Considered Harmful

GHVReposViewController

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { GHVRepo *repository = self.repositories[indexPath.row]; GHVRepoViewController *controller = [[GHVRepoViewController alloc] initWithRepository:repository]; [self.navigationController pushViewController:controller animated:YES]; }

UITableViewDelegate Responsibilities

Page 36: Apple Templates Considered Harmful

GHVReposViewController

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { GHVRepo *repository = self.repositories[indexPath.row]; GHVRepoViewController *controller = [[GHVRepoViewController alloc] initWithRepository:repository]; [self.navigationController pushViewController:controller animated:YES]; }

UITableViewDelegate Responsibilities

Page 37: Apple Templates Considered Harmful

GHVReposViewController

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { GHVRepo *repository = self.repositories[indexPath.row]; GHVRepoViewController *controller = [[GHVRepoViewController alloc] initWithRepository:repository]; [self.navigationController pushViewController:controller animated:YES]; }

UITableViewDelegate Responsibilities

Page 38: Apple Templates Considered Harmful

GHVReposViewController

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { GHVRepo *repository = self.repositories[indexPath.row]; GHVRepoViewController *controller = [[GHVRepoViewController alloc] initWithRepository:repository]; [self.navigationController pushViewController:controller animated:YES]; }

UITableViewDelegate Responsibilities

Page 39: Apple Templates Considered Harmful

GHVReposViewController

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { GHVRepo *repository = self.repositories[indexPath.row]; GHVRepoViewController *controller = [[GHVRepoViewController alloc] initWithRepository:repository]; [self.navigationController pushViewController:controller animated:YES]; }

UITableViewDelegate Responsibilities

Page 40: Apple Templates Considered Harmful

GHVReposViewController

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { GHVRepo *repository = self.repositories[indexPath.row]; GHVRepoViewController *controller = [[GHVRepoViewController alloc] initWithRepository:repository]; [self.navigationController pushViewController:controller animated:YES]; }

UITableViewDelegate Responsibilities

Page 41: Apple Templates Considered Harmful

GHVReposViewController

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { GHVRepo *repository = self.repositories[indexPath.row]; GHVRepoViewController *controller = [[GHVRepoViewController alloc] initWithRepository:repository]; [self.navigationController pushViewController:controller animated:YES]; }

UITableViewDelegate Responsibilities

Page 42: Apple Templates Considered Harmful

GHVReposViewController

Page 43: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities

Page 44: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Kick off repository fetch

Page 45: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Kick off repository fetch• Show network activity indicators

Page 46: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Kick off repository fetch• Show network activity indicators

2. UITableViewDataSource responsibilities

Page 47: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Kick off repository fetch• Show network activity indicators

2. UITableViewDataSource responsibilities• Store repositories

Page 48: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Kick off repository fetch• Show network activity indicators

2. UITableViewDataSource responsibilities• Store repositories• Specify how many sections in table view

Page 49: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Kick off repository fetch• Show network activity indicators

2. UITableViewDataSource responsibilities• Store repositories• Specify how many sections in table view• Specify how many rows in each section

Page 50: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Kick off repository fetch• Show network activity indicators

2. UITableViewDataSource responsibilities• Store repositories• Specify how many sections in table view• Specify how many rows in each section• Create each cell based on repository data

Page 51: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Kick off repository fetch• Show network activity indicators

2. UITableViewDataSource responsibilities• Store repositories• Specify how many sections in table view• Specify how many rows in each section• Create each cell based on repository data

3. UITableViewDelegate responsibilities

Page 52: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Kick off repository fetch• Show network activity indicators

2. UITableViewDataSource responsibilities• Store repositories• Specify how many sections in table view• Specify how many rows in each section• Create each cell based on repository data

3. UITableViewDelegate responsibilities• Push view controller for selected repository

Page 53: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Kick off repository fetch• Show network activity indicators

2. UITableViewDataSource responsibilities• Store repositories• Specify how many sections in table view• Specify how many rows in each section• Create each cell based on repository data

3. UITableViewDelegate responsibilities• Push view controller for selected repository

100+ lines of code

Page 54: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Kick off repository fetch• Show network activity indicators

2. UITableViewDataSource responsibilities• Store repositories• Specify how many sections in table view• Specify how many rows in each section• Create each cell based on repository data

3. UITableViewDelegate responsibilities• Push view controller for selected repository

100+ lines of code

Page 55: Apple Templates Considered Harmful

GHVReposViewControllerWhat a Bloated View Controller Looks Like

Page 56: Apple Templates Considered Harmful

GHVReposViewControllerSeparation of Concerns

Page 57: Apple Templates Considered Harmful

GHVReposViewControllerSeparation of Concerns

Page 58: Apple Templates Considered Harmful

GHVReposViewControllerSeparation of Concerns

Page 59: Apple Templates Considered Harmful

GHVRepoStoreA UITableViewDataStore and Nothing More

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } !- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } !- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /// Dequeue, configure, and return a cell } !- (void)reloadRepositories:(GHVRepoStoreSuccessBlock)success failure:(GHVRepoStoreFailureBlock)failure { [self.apiClient allRepositoriesForUsername:self.username success:/* ... */ failure:/* ... */]; }

Page 60: Apple Templates Considered Harmful

GHVRepoStoreA UITableViewDataStore and Nothing More

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } !- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } !- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /// Dequeue, configure, and return a cell } !- (void)reloadRepositories:(GHVRepoStoreSuccessBlock)success failure:(GHVRepoStoreFailureBlock)failure { [self.apiClient allRepositoriesForUsername:self.username success:/* ... */ failure:/* ... */]; }

Page 61: Apple Templates Considered Harmful

GHVRepoStoreA UITableViewDataStore and Nothing More

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } !- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } !- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /// Dequeue, configure, and return a cell } !- (void)reloadRepositories:(GHVRepoStoreSuccessBlock)success failure:(GHVRepoStoreFailureBlock)failure { [self.apiClient allRepositoriesForUsername:self.username success:/* ... */ failure:/* ... */]; }

Page 62: Apple Templates Considered Harmful

GHVRepoStoreA UITableViewDataStore and Nothing More

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } !- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } !- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /// Dequeue, configure, and return a cell } !- (void)reloadRepositories:(GHVRepoStoreSuccessBlock)success failure:(GHVRepoStoreFailureBlock)failure { [self.apiClient allRepositoriesForUsername:self.username success:/* ... */ failure:/* ... */]; }

Page 63: Apple Templates Considered Harmful

GHVRepoStoreA UITableViewDataStore and Nothing More

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } !- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } !- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /// Dequeue, configure, and return a cell } !- (void)reloadRepositories:(GHVRepoStoreSuccessBlock)success failure:(GHVRepoStoreFailureBlock)failure { [self.apiClient allRepositoriesForUsername:self.username success:/* ... */ failure:/* ... */]; }

Page 64: Apple Templates Considered Harmful

GHVReposViewControllerTransferring Responsibilities to the Store

- (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } !- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; !!!!!!!!}

Page 65: Apple Templates Considered Harmful

GHVReposViewControllerTransferring Responsibilities to the Store

- (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } !- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; !!!!!!!!}

Page 66: Apple Templates Considered Harmful

GHVReposViewControllerTransferring Responsibilities to the Store

- (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } !- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; !!!!!!!!}

Page 67: Apple Templates Considered Harmful

GHVReposViewControllerTransferring Responsibilities to the Store

- (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } !- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; !!!!!!!!}

Page 68: Apple Templates Considered Harmful

GHVReposViewControllerTransferring Responsibilities to the Store

- (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } !- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; !!!!!!!!}

Page 69: Apple Templates Considered Harmful

GHVReposViewControllerTransferring Responsibilities to the Store

- (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } !- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; !!!!!!!!}

Page 70: Apple Templates Considered Harmful

GHVReposViewControllerTransferring Responsibilities to the Store

- (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } !- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; !!!!!!!!}

self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];

Page 71: Apple Templates Considered Harmful

GHVReposViewControllerTransferring Responsibilities to the Store

- (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } !- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; !!!!!!!!}

self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];

Page 72: Apple Templates Considered Harmful

GHVReposViewControllerTransferring Responsibilities to the Store

- (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } !- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; !!!!!!!!}

self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];

Page 73: Apple Templates Considered Harmful

GHVReposViewControllerTransferring Responsibilities to the Store

- (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } !- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; !!!!!!!!}

self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];

Page 74: Apple Templates Considered Harmful

GHVReposViewControllerTransferring Responsibilities to the Store

- (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } !- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; !!!!!!!!}

self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];

Page 75: Apple Templates Considered Harmful

GHVReposViewControllerTransferring Responsibilities to the Store

- (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } !- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; !!!!!!!!}

self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];

Page 76: Apple Templates Considered Harmful

GHVReposViewControllerTransferring Responsibilities to the Store

- (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } !- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; !!!!!!!!}

self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];

Page 77: Apple Templates Considered Harmful

GHVReposViewControllerTransferring Responsibilities to the Store

- (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } !- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; !!!!!!!!}

self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];

Page 78: Apple Templates Considered Harmful

GHVReposViewControllerTransferring Responsibilities to the Store

- (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } !- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; !!!!!!!!}

self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];

Page 79: Apple Templates Considered Harmful

GHVReposViewController

Page 80: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities

Page 81: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Connect table view to data store

Page 82: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Connect table view to data store• Show network activity indicators

Page 83: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Connect table view to data store• Show network activity indicators

2. UITableViewDelegate responsibilities

Page 84: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Connect table view to data store• Show network activity indicators

2. UITableViewDelegate responsibilities• Push view controller for selected repository

Page 85: Apple Templates Considered Harmful

GHVReposViewController

1. View controller responsibilities• Connect table view to data store• Show network activity indicators

2. UITableViewDelegate responsibilities• Push view controller for selected repository

Less than 100 lines of code

Page 86: Apple Templates Considered Harmful

Single Responsibility PrincipleBenefits

Page 87: Apple Templates Considered Harmful

Single Responsibility Principle

• Less brittle

Benefits

Page 88: Apple Templates Considered Harmful

Single Responsibility Principle

• Less brittle• More modular

Benefits

Page 89: Apple Templates Considered Harmful

Single Responsibility Principle

• Less brittle• More modular• Easier to read, understand, and debug

Benefits

Page 90: Apple Templates Considered Harmful

The Problem with Apple TemplatesIf Only They Knew About the SRP

Page 91: Apple Templates Considered Harmful

The Problem with Apple TemplatesIf Only They Knew About the SRP

• Master-Detail Application (with Core Data) • AppDelegate

• Sets up UIWindow root view controller • Sets up Core Data stack, handles errors • 100+ lines of code

Page 92: Apple Templates Considered Harmful

The Problem with Apple TemplatesIf Only They Knew About the SRP

• Master-Detail Application (with Core Data) • AppDelegate

• Sets up UIWindow root view controller • Sets up Core Data stack, handles errors • 100+ lines of code

• OpenGL Application • View controller does it all!

• Sets up OpenGL context • Compiles shaders • Stores vertex data • 400+ lines of code

Page 93: Apple Templates Considered Harmful

Not All Templates Created EqualSome Templates Employ Modular Design

Page 94: Apple Templates Considered Harmful

Not All Templates Created EqualSome Templates Employ Modular Design

• Page-Based Application • Separates concerns among UIPageViewDelegate and UIPageViewDataSource

• Small classes

Page 95: Apple Templates Considered Harmful

Not All Templates Created EqualSome Templates Employ Modular Design

• Page-Based Application • Separates concerns among UIPageViewDelegate and UIPageViewDataSource

• Small classes• SpriteKit Game

• Small classes with a relatively clear separation of concerns

Page 96: Apple Templates Considered Harmful

Takeaways

• Single responsibility principle • Classes should have one, and only one, reason to change

• Apple’s templates should be thought of as “proof of concepts”, not as examples of clean, well-structured code

• Just because Apple does it doesn’t mean it’s a good idea • Use your better judgement on what’s “clean code” and what isn’t

Page 97: Apple Templates Considered Harmful

Want More on Clean Code?

• Slides for this talk available at http://modocache.io/apple-templates-considered-harmful

• Follow me on Twitter and GitHub at @modocache • Clean Code Resources

• Follow Robert C. Martin at @unclebobmartin • More on Object-Oriented Design at http://www.butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

• Clean Code videos at http://cleancoders.com/ • Building a Healthy Mistrust of Apple Engineering

• Follow Peter Steinberger at @steipete • Follow Justin Spahr-Summers at @ jspahrsummers