テンプレートは有害と考えられる Appleのテンプレートと責任単一原則 Brian Gesiak 2014年3月28日 リクルート株式会社 研究生、東京大学 @modocache
テンプレートは有害と考えられるAppleのテンプレートと責任単一原則
Brian Gesiak
2014年3月28日 リクルート株式会社
研究生、東京大学 @modocache
内容•単一責任原則
• The single responsibility principle、またはSRP
• UITableViewControllerのファイル・テンプレート •モデルとコントローラの役割を両方果たしている
•責任を分担させる一例 • GitHubViewer.appのview controllerからUITableViewDataStoreのコードを取り出す
• Appleのファイルやプロジェクト・テンプレートについて
•その多くは単一責任原則(SRP)に違反する •実装の一例であり、プロダクション・コードで真似すべきではない
単一責任原則
クラスを変更する理由は一つ以上存在してはならない
- Robert C. Martin
Single Responsibility Principle
Robert C. Martin (Uncle Bob)
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 { /* ... */ }
テンプレートの内容
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 { /* ... */ }
テンプレートの内容
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 { /* ... */ }
テンプレートの内容
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 { /* ... */ }
テンプレートの内容
UITableViewDelegate
UITableViewDataSource
UITableViewController責任(変更すべき理由)
1. テーブル内で表示されるデータを決定する !
!
2. ユーザがテーブルをタップするときの動作などを決定する
UITableViewDelegate
UITableViewDataSource
UITableViewController責任(変更すべき理由)
1. テーブル内で表示されるデータを決定する !
!
2. ユーザがテーブルをタップするときの動作などを決定する
UITableViewDelegate
UITableViewDataSource
UITableViewController責任(変更すべき理由)
1. テーブル内で表示されるデータを決定する !
!
2. ユーザがテーブルをタップするときの動作などを決定する
UITableViewDelegate
UITableViewDataSource
UITableViewController責任(変更すべき理由)
1. テーブル内で表示されるデータを決定する !
!
2. ユーザがテーブルをタップするときの動作などを決定する
GitHubViewer
GitHubのアイコンは@peterhajasによる著作物であり、Creative Commons
License version 3.0のもとで公開されています。
GitHubViewer
GitHubのアイコンは@peterhajasによる著作物であり、Creative Commons
License version 3.0のもとで公開されています。
GHVReposViewController
- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; }
View Controllerの責任
GHVReposViewController
- (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; }
View Controllerの責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
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の責任
GHVReposViewController
GHVReposViewController
1. View controllerの責任
GHVReposViewController
1. View controllerの責任• レポを取得させるようにメッセージを送る
GHVReposViewController
1. View controllerの責任• レポを取得させるようにメッセージを送る• 通信インジケーターを表示させる
GHVReposViewController
1. View controllerの責任• レポを取得させるようにメッセージを送る• 通信インジケーターを表示させる
2. UITableViewDataSourceの責任
GHVReposViewController
1. View controllerの責任• レポを取得させるようにメッセージを送る• 通信インジケーターを表示させる
2. UITableViewDataSourceの責任• レポのデータを持つ
GHVReposViewController
1. View controllerの責任• レポを取得させるようにメッセージを送る• 通信インジケーターを表示させる
2. UITableViewDataSourceの責任• レポのデータを持つ• テーブルビューのsectionの数を決定する
GHVReposViewController
1. View controllerの責任• レポを取得させるようにメッセージを送る• 通信インジケーターを表示させる
2. UITableViewDataSourceの責任• レポのデータを持つ• テーブルビューのsectionの数を決定する• テーブルビューのrowsの数を決定する
GHVReposViewController
1. View controllerの責任• レポを取得させるようにメッセージを送る• 通信インジケーターを表示させる
2. UITableViewDataSourceの責任• レポのデータを持つ• テーブルビューのsectionの数を決定する• テーブルビューのrowsの数を決定する• レポのデータをもとにセルのビュー構成を構築する
GHVReposViewController
1. View controllerの責任• レポを取得させるようにメッセージを送る• 通信インジケーターを表示させる
2. UITableViewDataSourceの責任• レポのデータを持つ• テーブルビューのsectionの数を決定する• テーブルビューのrowsの数を決定する• レポのデータをもとにセルのビュー構成を構築する
3. UITableViewDelegateの責任
GHVReposViewController
1. View controllerの責任• レポを取得させるようにメッセージを送る• 通信インジケーターを表示させる
2. UITableViewDataSourceの責任• レポのデータを持つ• テーブルビューのsectionの数を決定する• テーブルビューのrowsの数を決定する• レポのデータをもとにセルのビュー構成を構築する
3. UITableViewDelegateの責任• タップしたレポを表示するためのview controllerをプッシュする
GHVReposViewController
1. View controllerの責任• レポを取得させるようにメッセージを送る• 通信インジケーターを表示させる
2. UITableViewDataSourceの責任• レポのデータを持つ• テーブルビューのsectionの数を決定する• テーブルビューのrowsの数を決定する• レポのデータをもとにセルのビュー構成を構築する
3. UITableViewDelegateの責任• タップしたレポを表示するためのview controllerをプッシュする
コードが100行以上にも及ぶ
GHVReposViewController
1. View controllerの責任• レポを取得させるようにメッセージを送る• 通信インジケーターを表示させる
2. UITableViewDataSourceの責任• レポのデータを持つ• テーブルビューのsectionの数を決定する• テーブルビューのrowsの数を決定する• レポのデータをもとにセルのビュー構成を構築する
3. UITableViewDelegateの責任• タップしたレポを表示するためのview controllerをプッシュする
コードが100行以上にも及ぶ
GHVReposViewController肥大化したview controllerの図
GHVReposViewController責任分担
GHVReposViewController責任分担
GHVReposViewController責任分担
GHVRepoStoreUITableViewDataStoreの責任のみを果たす
- (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:/* ... */]; }
GHVRepoStoreUITableViewDataStoreの責任のみを果たす
- (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:/* ... */]; }
GHVRepoStoreUITableViewDataStoreの責任のみを果たす
- (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:/* ... */]; }
GHVRepoStoreUITableViewDataStoreの責任のみを果たす
- (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:/* ... */]; }
GHVRepoStoreUITableViewDataStoreの責任のみを果たす
- (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:/* ... */]; }
GHVReposViewControllerGHVRepoStoreへの責任分担
- (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]; !!!!!!!!}
GHVReposViewControllerGHVRepoStoreへの責任分担
- (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]; !!!!!!!!}
GHVReposViewControllerGHVRepoStoreへの責任分担
- (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]; !!!!!!!!}
GHVReposViewControllerGHVRepoStoreへの責任分担
- (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]; !!!!!!!!}
GHVReposViewControllerGHVRepoStoreへの責任分担
- (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]; !!!!!!!!}
GHVReposViewControllerGHVRepoStoreへの責任分担
- (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]; !!!!!!!!}
GHVReposViewControllerGHVRepoStoreへの責任分担
- (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]; }];
GHVReposViewControllerGHVRepoStoreへの責任分担
- (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]; }];
GHVReposViewControllerGHVRepoStoreへの責任分担
- (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]; }];
GHVReposViewControllerGHVRepoStoreへの責任分担
- (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]; }];
GHVReposViewControllerGHVRepoStoreへの責任分担
- (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]; }];
GHVReposViewControllerGHVRepoStoreへの責任分担
- (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]; }];
GHVReposViewControllerGHVRepoStoreへの責任分担
- (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]; }];
GHVReposViewControllerGHVRepoStoreへの責任分担
- (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]; }];
GHVReposViewControllerGHVRepoStoreへの責任分担
- (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]; }];
GHVReposViewController
GHVReposViewController
1. View controllerの責任
GHVReposViewController
1. View controllerの責任• レポを取得させるようにメッセージする
GHVReposViewController
1. View controllerの責任• レポを取得させるようにメッセージする• 通信インジケーターを表示させる
GHVReposViewController
1. View controllerの責任• レポを取得させるようにメッセージする• 通信インジケーターを表示させる
2. UITableViewDelegateの責任
GHVReposViewController
1. View controllerの責任• レポを取得させるようにメッセージする• 通信インジケーターを表示させる
2. UITableViewDelegateの責任• タップしたレポを表示するためのview controllerをプッシュする
GHVReposViewController
コードは100行にも満たない1. View controllerの責任
• レポを取得させるようにメッセージする• 通信インジケーターを表示させる
2. UITableViewDelegateの責任• タップしたレポを表示するためのview controllerをプッシュする
責任単一原則メリット
責任単一原則
•実装の変更があった場合は小さなクラスの中で収まるのでいろんなところが壊れる可能性も低くなる
メリット
責任単一原則
•実装の変更があった場合は小さなクラスの中で収まるのでいろんなところが壊れる可能性も低くなる
•責任が明確なクラスで構成されているのでコードが読みやすくて、デバッグもしやすい
メリット
Appleのテンプレートの問題点SRPに違反している例
Appleのテンプレートの問題点SRPに違反している例• Master-Detail Application (with Core Data)
• AppDelegate • UIWindowのroot view controllerの初期化
• Core Data関連のセットアップ、エラーハンドリング
• 100行以上
Appleのテンプレートの問題点SRPに違反している例• Master-Detail Application (with Core Data)
• AppDelegate • UIWindowのroot view controllerの初期化
• Core Data関連のセットアップ、エラーハンドリング
• 100行以上• OpenGL Application
•ひとつのView controllerがすべての責任を負っている
• OpenGLのcontextの初期化
• Shadersのコンパイル
• Vertexのデータも持っている
• 400行以上
クリーンなテンプレート実装がそれほどひどくない例
クリーンなテンプレート実装がそれほどひどくない例• Page-Based Application
• UIPageViewDelegateとUIPageViewDataSourceの責任分担をしている
•それぞれのクラスも責任がはっきりしていて簡潔
クリーンなテンプレート実装がそれほどひどくない例• Page-Based Application
• UIPageViewDelegateとUIPageViewDataSourceの責任分担をしている
•それぞれのクラスも責任がはっきりしていて簡潔• SpriteKit Game
•それぞれのクラスが小さく、責任分担ができている
要約
•単一責任原則 •クラスを変更する理由は一つ以上存在してはならない
• Appleのテンプレートは真似すべきものではなく、実装の一例であるにすぎないと考えたほうがいい
• Appleのコードだからといってよく書かれているコードだというわけではない
•何がよく書かれているコードで、何がそうでないのかは自分で判断する必要がある
最後に•本日のスライド: http://modocache.io/apple-templates-considered-harmful
• Twitter、GitHubでフォローしてください:@modocache •クリーンコード
• Robert C. Martin:@unclebobmartin
• Object-Oriented Design:http://www.butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
• Clean Codeのビデオ:http://cleancoders.com/
• Appleのエンジニアリングの適当さを知るには
• Peter Steinberger:@steipete
• Justin Spahr-Summers:@ jspahrsummers