Your SlideShare is downloading. ×
  • Like
アップルのテンプレートは有害と考えられる
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Now you can save presentations on your phone or tablet

Available for both IPhone and Android

Text the download link to your phone

Standard text messaging rates apply

アップルのテンプレートは有害と考えられる

  • 12,410 views
Published

 

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
12,410
On SlideShare
0
From Embeds
0
Number of Embeds
11

Actions

Shares
Downloads
9
Comments
0
Likes
9

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. テンプレートは有害と考えられる Appleのテンプレートと責任単一原則 Brian Gesiak 2014年3月28日 リクルート株式会社 研究生、東京大学 @modocache
  • 2. 内容 • 単一責任原則 • The single responsibility principle、またはSRP • UITableViewControllerのファイル・テンプレート • モデルとコントローラの役割を両方果たしている • 責任を分担させる一例 • GitHubViewer.appのview controllerからUITableViewDataStoreの コードを取り出す • Appleのファイルやプロジェクト・テンプレートについて • その多くは単一責任原則(SRP)に違反する • 実装の一例であり、プロダクション・コードで真似すべきでは ない
  • 3. 単一責任原則 クラスを変更する理由は一つ以上存 在してはならない - Robert C. Martin Single Responsibility Principle
  • 4. Robert C. Martin (Uncle Bob)
  • 5. 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 { /* ... */ } テンプレートの内容
  • 6. 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 { /* ... */ } テンプレートの内容
  • 7. 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 { /* ... */ } テンプレートの内容
  • 8. 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 { /* ... */ } テンプレートの内容
  • 9. UITableViewDelegate UITableViewDataSource UITableViewController 責任(変更すべき理由) 1. テーブル内で表示されるデータを決定する ! ! 2. ユーザがテーブルをタップするときの動作などを 決定する
  • 10. UITableViewDelegate UITableViewDataSource UITableViewController 責任(変更すべき理由) 1. テーブル内で表示されるデータを決定する ! ! 2. ユーザがテーブルをタップするときの動作などを 決定する
  • 11. UITableViewDelegate UITableViewDataSource UITableViewController 責任(変更すべき理由) 1. テーブル内で表示されるデータを決定する ! ! 2. ユーザがテーブルをタップするときの動作などを 決定する
  • 12. UITableViewDelegate UITableViewDataSource UITableViewController 責任(変更すべき理由) 1. テーブル内で表示されるデータを決定する ! ! 2. ユーザがテーブルをタップするときの動作などを 決定する
  • 13. GitHubViewer GitHubのアイコンは@peterhajasによ る著作物であり、Creative Commons License version 3.0のもとで公開されて います。
  • 14. GitHubViewer GitHubのアイコンは@peterhajasによ る著作物であり、Creative Commons License version 3.0のもとで公開されて います。
  • 15. GHVReposViewController - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; } View Controllerの責任
  • 16. GHVReposViewController - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; } View Controllerの責任
  • 17. 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の責任
  • 18. 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の責任
  • 19. 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の責任
  • 20. 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の責任
  • 21. 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の責任
  • 22. 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の責任
  • 23. 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の責任
  • 24. 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の責任
  • 25. 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の責任
  • 26. 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の責任
  • 27. 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の責任
  • 28. 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の責任
  • 29. 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の責任
  • 30. 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の責任
  • 31. 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の責任
  • 32. 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の責任
  • 33. 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の責任
  • 34. 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の責任
  • 35. 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の責任
  • 36. 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の責任
  • 37. 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の責任
  • 38. 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の責任
  • 39. 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の責任
  • 40. 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の責任
  • 41. 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の責任
  • 42. 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の責任
  • 43. GHVReposViewController
  • 44. GHVReposViewController 1. View controllerの責任
  • 45. GHVReposViewController 1. View controllerの責任 • レポを取得させるようにメッセージを送る
  • 46. GHVReposViewController 1. View controllerの責任 • レポを取得させるようにメッセージを送る • 通信インジケーターを表示させる
  • 47. GHVReposViewController 1. View controllerの責任 • レポを取得させるようにメッセージを送る • 通信インジケーターを表示させる 2. UITableViewDataSourceの責任
  • 48. GHVReposViewController 1. View controllerの責任 • レポを取得させるようにメッセージを送る • 通信インジケーターを表示させる 2. UITableViewDataSourceの責任 • レポのデータを持つ
  • 49. GHVReposViewController 1. View controllerの責任 • レポを取得させるようにメッセージを送る • 通信インジケーターを表示させる 2. UITableViewDataSourceの責任 • レポのデータを持つ • テーブルビューのsectionの数を決定する
  • 50. GHVReposViewController 1. View controllerの責任 • レポを取得させるようにメッセージを送る • 通信インジケーターを表示させる 2. UITableViewDataSourceの責任 • レポのデータを持つ • テーブルビューのsectionの数を決定する • テーブルビューのrowsの数を決定する
  • 51. GHVReposViewController 1. View controllerの責任 • レポを取得させるようにメッセージを送る • 通信インジケーターを表示させる 2. UITableViewDataSourceの責任 • レポのデータを持つ • テーブルビューのsectionの数を決定する • テーブルビューのrowsの数を決定する • レポのデータをもとにセルのビュー構成を構築する
  • 52. GHVReposViewController 1. View controllerの責任 • レポを取得させるようにメッセージを送る • 通信インジケーターを表示させる 2. UITableViewDataSourceの責任 • レポのデータを持つ • テーブルビューのsectionの数を決定する • テーブルビューのrowsの数を決定する • レポのデータをもとにセルのビュー構成を構築する 3. UITableViewDelegateの責任
  • 53. GHVReposViewController 1. View controllerの責任 • レポを取得させるようにメッセージを送る • 通信インジケーターを表示させる 2. UITableViewDataSourceの責任 • レポのデータを持つ • テーブルビューのsectionの数を決定する • テーブルビューのrowsの数を決定する • レポのデータをもとにセルのビュー構成を構築する 3. UITableViewDelegateの責任 • タップしたレポを表示するためのview controllerをプッシュする
  • 54. GHVReposViewController 1. View controllerの責任 • レポを取得させるようにメッセージを送る • 通信インジケーターを表示させる 2. UITableViewDataSourceの責任 • レポのデータを持つ • テーブルビューのsectionの数を決定する • テーブルビューのrowsの数を決定する • レポのデータをもとにセルのビュー構成を構築する 3. UITableViewDelegateの責任 • タップしたレポを表示するためのview controllerをプッシュする コードが100行以上にも及ぶ
  • 55. GHVReposViewController 1. View controllerの責任 • レポを取得させるようにメッセージを送る • 通信インジケーターを表示させる 2. UITableViewDataSourceの責任 • レポのデータを持つ • テーブルビューのsectionの数を決定する • テーブルビューのrowsの数を決定する • レポのデータをもとにセルのビュー構成を構築する 3. UITableViewDelegateの責任 • タップしたレポを表示するためのview controllerをプッシュする コードが100行以上にも及ぶ
  • 56. GHVReposViewController 肥大化したview controllerの図
  • 57. GHVReposViewController 責任分担
  • 58. GHVReposViewController 責任分担
  • 59. GHVReposViewController 責任分担
  • 60. GHVRepoStore UITableViewDataStoreの責任のみを果たす - (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:/* ... */]; }
  • 61. GHVRepoStore UITableViewDataStoreの責任のみを果たす - (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:/* ... */]; }
  • 62. GHVRepoStore UITableViewDataStoreの責任のみを果たす - (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:/* ... */]; }
  • 63. GHVRepoStore UITableViewDataStoreの責任のみを果たす - (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:/* ... */]; }
  • 64. GHVRepoStore UITableViewDataStoreの責任のみを果たす - (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:/* ... */]; }
  • 65. GHVReposViewController GHVRepoStoreへの責任分担 - (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]; ! ! ! ! ! ! ! ! }
  • 66. GHVReposViewController GHVRepoStoreへの責任分担 - (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]; ! ! ! ! ! ! ! ! }
  • 67. GHVReposViewController GHVRepoStoreへの責任分担 - (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]; ! ! ! ! ! ! ! ! }
  • 68. GHVReposViewController GHVRepoStoreへの責任分担 - (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]; ! ! ! ! ! ! ! ! }
  • 69. GHVReposViewController GHVRepoStoreへの責任分担 - (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]; ! ! ! ! ! ! ! ! }
  • 70. GHVReposViewController GHVRepoStoreへの責任分担 - (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]; ! ! ! ! ! ! ! ! }
  • 71. GHVReposViewController GHVRepoStoreへの責任分担 - (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]; }];
  • 72. GHVReposViewController GHVRepoStoreへの責任分担 - (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]; }];
  • 73. GHVReposViewController GHVRepoStoreへの責任分担 - (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]; }];
  • 74. GHVReposViewController GHVRepoStoreへの責任分担 - (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]; }];
  • 75. GHVReposViewController GHVRepoStoreへの責任分担 - (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]; }];
  • 76. GHVReposViewController GHVRepoStoreへの責任分担 - (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]; }];
  • 77. GHVReposViewController GHVRepoStoreへの責任分担 - (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]; }];
  • 78. GHVReposViewController GHVRepoStoreへの責任分担 - (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]; }];
  • 79. GHVReposViewController GHVRepoStoreへの責任分担 - (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]; }];
  • 80. GHVReposViewController
  • 81. GHVReposViewController 1. View controllerの責任
  • 82. GHVReposViewController 1. View controllerの責任 • レポを取得させるようにメッセージする
  • 83. GHVReposViewController 1. View controllerの責任 • レポを取得させるようにメッセージする • 通信インジケーターを表示させる
  • 84. GHVReposViewController 1. View controllerの責任 • レポを取得させるようにメッセージする • 通信インジケーターを表示させる 2. UITableViewDelegateの責任
  • 85. GHVReposViewController 1. View controllerの責任 • レポを取得させるようにメッセージする • 通信インジケーターを表示させる 2. UITableViewDelegateの責任 • タップしたレポを表示するためのview controllerを プッシュする
  • 86. GHVReposViewController コードは100行にも満たない 1. View controllerの責任 • レポを取得させるようにメッセージする • 通信インジケーターを表示させる 2. UITableViewDelegateの責任 • タップしたレポを表示するためのview controllerを プッシュする
  • 87. 責任単一原則 メリット
  • 88. 責任単一原則 • 実装の変更があった場合は小さなクラスの中で収ま るのでいろんなところが壊れる可能性も低くなる メリット
  • 89. 責任単一原則 • 実装の変更があった場合は小さなクラスの中で収ま るのでいろんなところが壊れる可能性も低くなる • 責任が明確なクラスで構成されているのでコードが 読みやすくて、デバッグもしやすい メリット
  • 90. Appleのテンプレートの問題点 SRPに違反している例
  • 91. Appleのテンプレートの問題点 SRPに違反している例 • Master-Detail Application (with Core Data) • AppDelegate • UIWindowのroot view controllerの初期化 • Core Data関連のセットアップ、エラーハンドリング • 100行以上
  • 92. 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行以上
  • 93. クリーンなテンプレート 実装がそれほどひどくない例
  • 94. クリーンなテンプレート 実装がそれほどひどくない例 • Page-Based Application • UIPageViewDelegateとUIPageViewDataSourceの責任 分担をしている • それぞれのクラスも責任がはっきりしていて簡潔
  • 95. クリーンなテンプレート 実装がそれほどひどくない例 • Page-Based Application • UIPageViewDelegateとUIPageViewDataSourceの責任 分担をしている • それぞれのクラスも責任がはっきりしていて簡潔 • SpriteKit Game • それぞれのクラスが小さく、責任分担ができてい る
  • 96. 要約 • 単一責任原則 • クラスを変更する理由は一つ以上存在してはならな い • Appleのテンプレートは真似すべきものではなく、実装 の一例であるにすぎないと考えたほうがいい • Appleのコードだからといってよく書かれているコード だというわけではない • 何がよく書かれているコードで、何がそうでないの かは自分で判断する必要がある
  • 97. 最後に • 本日のスライド: 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