Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

15,958 views

Published on

  • Be the first to comment

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

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

×