SlideShare une entreprise Scribd logo
1  sur  97
Télécharger pour lire hors ligne
テンプレートは有害と考えられる
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
責任分担
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:/* ... */];
}
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:/* ... */];
}
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:/* ... */];
}
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:/* ... */];
}
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:/* ... */];
}
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];
!
!
!
!
!
!
!
!
}
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];
!
!
!
!
!
!
!
!
}
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];
!
!
!
!
!
!
!
!
}
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];
!
!
!
!
!
!
!
!
}
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];
!
!
!
!
!
!
!
!
}
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];
!
!
!
!
!
!
!
!
}
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];
}];
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];
}];
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];
}];
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];
}];
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];
}];
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];
}];
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];
}];
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];
}];
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];
}];
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

Contenu connexe

Tendances

TDD勉強会キックオフ for Java
TDD勉強会キックオフ for JavaTDD勉強会キックオフ for Java
TDD勉強会キックオフ for JavaYuta Kawadai
 
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~C# JobSystem 編~
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~C# JobSystem 編~【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~C# JobSystem 編~
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~C# JobSystem 編~Unity Technologies Japan K.K.
 
Paging Libraryの利用をやめたいお気持ち表明
Paging Libraryの利用をやめたいお気持ち表明Paging Libraryの利用をやめたいお気持ち表明
Paging Libraryの利用をやめたいお気持ち表明furusin
 
Unit test in android
Unit test in androidUnit test in android
Unit test in androidTatsuya Maki
 
Selenium webdriver使ってみようず
Selenium webdriver使ってみようずSelenium webdriver使ってみようず
Selenium webdriver使ってみようずOda Shinsuke
 
Selenium webdriver使ってみようず
Selenium webdriver使ってみようずSelenium webdriver使ってみようず
Selenium webdriver使ってみようずOda Shinsuke
 
CakePHP2 Loading (Japanese)
CakePHP2 Loading (Japanese)CakePHP2 Loading (Japanese)
CakePHP2 Loading (Japanese)ichikaway
 
Spring bootでweb バリデート編
Spring bootでweb バリデート編Spring bootでweb バリデート編
Spring bootでweb バリデート編なべ
 
G*workshop 2011/11/22 Geb+Betamax
G*workshop 2011/11/22 Geb+BetamaxG*workshop 2011/11/22 Geb+Betamax
G*workshop 2011/11/22 Geb+BetamaxNobuhiro Sue
 
JavaScriptでWebDriverのテストコードを書きましょ
JavaScriptでWebDriverのテストコードを書きましょJavaScriptでWebDriverのテストコードを書きましょ
JavaScriptでWebDriverのテストコードを書きましょKohki Nakashima
 
Selenium 触ってみよう
Selenium 触ってみようSelenium 触ってみよう
Selenium 触ってみようOda Shinsuke
 
ソーシャルアプリ勉強会(第一回資料)配布用
ソーシャルアプリ勉強会(第一回資料)配布用ソーシャルアプリ勉強会(第一回資料)配布用
ソーシャルアプリ勉強会(第一回資料)配布用Yatabe Terumasa
 
React Native GUIDE
React Native GUIDEReact Native GUIDE
React Native GUIDEdcubeio
 
思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8
思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8
思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8y_taka_23
 
Gradle a new Generation Build Tool
Gradle a new Generation Build ToolGradle a new Generation Build Tool
Gradle a new Generation Build ToolShinya Mochida
 
Composable Callbacks & Listeners
Composable Callbacks & ListenersComposable Callbacks & Listeners
Composable Callbacks & ListenersTaisuke Oe
 

Tendances (20)

TDD勉強会キックオフ for Java
TDD勉強会キックオフ for JavaTDD勉強会キックオフ for Java
TDD勉強会キックオフ for Java
 
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~C# JobSystem 編~
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~C# JobSystem 編~【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~C# JobSystem 編~
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~C# JobSystem 編~
 
Paging Libraryの利用をやめたいお気持ち表明
Paging Libraryの利用をやめたいお気持ち表明Paging Libraryの利用をやめたいお気持ち表明
Paging Libraryの利用をやめたいお気持ち表明
 
Driverについて
DriverについてDriverについて
Driverについて
 
Unit test in android
Unit test in androidUnit test in android
Unit test in android
 
Selenium webdriver使ってみようず
Selenium webdriver使ってみようずSelenium webdriver使ってみようず
Selenium webdriver使ってみようず
 
Selenium webdriver使ってみようず
Selenium webdriver使ってみようずSelenium webdriver使ってみようず
Selenium webdriver使ってみようず
 
CakePHP2 Loading (Japanese)
CakePHP2 Loading (Japanese)CakePHP2 Loading (Japanese)
CakePHP2 Loading (Japanese)
 
Spring bootでweb バリデート編
Spring bootでweb バリデート編Spring bootでweb バリデート編
Spring bootでweb バリデート編
 
G*workshop 2011/11/22 Geb+Betamax
G*workshop 2011/11/22 Geb+BetamaxG*workshop 2011/11/22 Geb+Betamax
G*workshop 2011/11/22 Geb+Betamax
 
JavaScriptでWebDriverのテストコードを書きましょ
JavaScriptでWebDriverのテストコードを書きましょJavaScriptでWebDriverのテストコードを書きましょ
JavaScriptでWebDriverのテストコードを書きましょ
 
Selenium 触ってみよう
Selenium 触ってみようSelenium 触ってみよう
Selenium 触ってみよう
 
ソーシャルアプリ勉強会(第一回資料)配布用
ソーシャルアプリ勉強会(第一回資料)配布用ソーシャルアプリ勉強会(第一回資料)配布用
ソーシャルアプリ勉強会(第一回資料)配布用
 
fanscala1 3 sbt
fanscala1 3 sbtfanscala1 3 sbt
fanscala1 3 sbt
 
Maven2 plugin
Maven2 pluginMaven2 plugin
Maven2 plugin
 
React Native GUIDE
React Native GUIDEReact Native GUIDE
React Native GUIDE
 
思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8
思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8
思ったほど怖くない! Haskell on JVM 超入門 #jjug_ccc #ccc_l8
 
Gradle a new Generation Build Tool
Gradle a new Generation Build ToolGradle a new Generation Build Tool
Gradle a new Generation Build Tool
 
CLRH_120414_WFTDD
CLRH_120414_WFTDDCLRH_120414_WFTDD
CLRH_120414_WFTDD
 
Composable Callbacks & Listeners
Composable Callbacks & ListenersComposable Callbacks & Listeners
Composable Callbacks & Listeners
 

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

エンタープライズ分野での実践AngularJS
エンタープライズ分野での実践AngularJSエンタープライズ分野での実践AngularJS
エンタープライズ分野での実践AngularJSAyumi Goto
 
Rails基礎講座 part.2
Rails基礎講座 part.2Rails基礎講座 part.2
Rails基礎講座 part.2Jun Yokoyama
 
JavaScriptテンプレートエンジンで活かすData API
JavaScriptテンプレートエンジンで活かすData APIJavaScriptテンプレートエンジンで活かすData API
JavaScriptテンプレートエンジンで活かすData APIHajime Fujimoto
 
scala+liftで遊ぼう
scala+liftで遊ぼうscala+liftで遊ぼう
scala+liftで遊ぼうyouku
 
Angular2 rc.1 unit testing overview
Angular2 rc.1 unit testing overviewAngular2 rc.1 unit testing overview
Angular2 rc.1 unit testing overviewMitsuru Ogawa
 
自作node.jsフレームワークとnginxを使ってラジオサイトを作ってみた
自作node.jsフレームワークとnginxを使ってラジオサイトを作ってみた自作node.jsフレームワークとnginxを使ってラジオサイトを作ってみた
自作node.jsフレームワークとnginxを使ってラジオサイトを作ってみたYuki Takei
 
基礎から見直す ASP.NET MVC の単体テスト自動化方法 ~ Windows Azure 関連もあるかも~
基礎から見直す ASP.NET MVC の単体テスト自動化方法 ~ Windows Azure 関連もあるかも~基礎から見直す ASP.NET MVC の単体テスト自動化方法 ~ Windows Azure 関連もあるかも~
基礎から見直す ASP.NET MVC の単体テスト自動化方法 ~ Windows Azure 関連もあるかも~normalian
 
Rails and twitter #twtr_hack
Rails and twitter #twtr_hackRails and twitter #twtr_hack
Rails and twitter #twtr_hacki7a
 
データマイニング+WEB勉強会資料第6回
データマイニング+WEB勉強会資料第6回データマイニング+WEB勉強会資料第6回
データマイニング+WEB勉強会資料第6回Naoyuki Yamada
 
はじめてのCodeIgniter
はじめてのCodeIgniterはじめてのCodeIgniter
はじめてのCodeIgniterYuya Matsushima
 
Head toward Java 16 (Night Seminar Edition)
Head toward Java 16 (Night Seminar Edition)Head toward Java 16 (Night Seminar Edition)
Head toward Java 16 (Night Seminar Edition)Yuji Kubota
 
ハイブリッドアプリへのLocalytics導入ガイド
ハイブリッドアプリへのLocalytics導入ガイドハイブリッドアプリへのLocalytics導入ガイド
ハイブリッドアプリへのLocalytics導入ガイドLocalyticsJP
 
20091030cakephphandson 01
20091030cakephphandson 0120091030cakephphandson 01
20091030cakephphandson 01Yusuke Ando
 
React を導入した フロントエンド開発
React を導入したフロントエンド開発React を導入したフロントエンド開発
React を導入した フロントエンド開発 daisuke-a-matsui
 
Ruby on Rails Tutorial
Ruby on Rails TutorialRuby on Rails Tutorial
Ruby on Rails TutorialKen Iiboshi
 
Spring mvc
Spring mvcSpring mvc
Spring mvcRyo Asai
 

Similaire à アップルのテンプレートは有害と考えられる (20)

エンタープライズ分野での実践AngularJS
エンタープライズ分野での実践AngularJSエンタープライズ分野での実践AngularJS
エンタープライズ分野での実践AngularJS
 
Rails基礎講座 part.2
Rails基礎講座 part.2Rails基礎講座 part.2
Rails基礎講座 part.2
 
ASP.NET MVC 1.0
ASP.NET MVC 1.0ASP.NET MVC 1.0
ASP.NET MVC 1.0
 
JavaScriptテンプレートエンジンで活かすData API
JavaScriptテンプレートエンジンで活かすData APIJavaScriptテンプレートエンジンで活かすData API
JavaScriptテンプレートエンジンで活かすData API
 
scala+liftで遊ぼう
scala+liftで遊ぼうscala+liftで遊ぼう
scala+liftで遊ぼう
 
Angular2 rc.1 unit testing overview
Angular2 rc.1 unit testing overviewAngular2 rc.1 unit testing overview
Angular2 rc.1 unit testing overview
 
Blocksの活用法
Blocksの活用法Blocksの活用法
Blocksの活用法
 
自作node.jsフレームワークとnginxを使ってラジオサイトを作ってみた
自作node.jsフレームワークとnginxを使ってラジオサイトを作ってみた自作node.jsフレームワークとnginxを使ってラジオサイトを作ってみた
自作node.jsフレームワークとnginxを使ってラジオサイトを作ってみた
 
基礎から見直す ASP.NET MVC の単体テスト自動化方法 ~ Windows Azure 関連もあるかも~
基礎から見直す ASP.NET MVC の単体テスト自動化方法 ~ Windows Azure 関連もあるかも~基礎から見直す ASP.NET MVC の単体テスト自動化方法 ~ Windows Azure 関連もあるかも~
基礎から見直す ASP.NET MVC の単体テスト自動化方法 ~ Windows Azure 関連もあるかも~
 
Rails and twitter #twtr_hack
Rails and twitter #twtr_hackRails and twitter #twtr_hack
Rails and twitter #twtr_hack
 
データマイニング+WEB勉強会資料第6回
データマイニング+WEB勉強会資料第6回データマイニング+WEB勉強会資料第6回
データマイニング+WEB勉強会資料第6回
 
はじめてのCodeIgniter
はじめてのCodeIgniterはじめてのCodeIgniter
はじめてのCodeIgniter
 
Swiftyを試す
Swiftyを試すSwiftyを試す
Swiftyを試す
 
VerilatorとSystemC
VerilatorとSystemCVerilatorとSystemC
VerilatorとSystemC
 
Head toward Java 16 (Night Seminar Edition)
Head toward Java 16 (Night Seminar Edition)Head toward Java 16 (Night Seminar Edition)
Head toward Java 16 (Night Seminar Edition)
 
ハイブリッドアプリへのLocalytics導入ガイド
ハイブリッドアプリへのLocalytics導入ガイドハイブリッドアプリへのLocalytics導入ガイド
ハイブリッドアプリへのLocalytics導入ガイド
 
20091030cakephphandson 01
20091030cakephphandson 0120091030cakephphandson 01
20091030cakephphandson 01
 
React を導入した フロントエンド開発
React を導入したフロントエンド開発React を導入したフロントエンド開発
React を導入した フロントエンド開発
 
Ruby on Rails Tutorial
Ruby on Rails TutorialRuby on Rails Tutorial
Ruby on Rails Tutorial
 
Spring mvc
Spring mvcSpring mvc
Spring mvc
 

Plus de Brian Gesiak

Everything You (N)ever Wanted to Know about Testing View Controllers
Everything You (N)ever Wanted to Know about Testing View ControllersEverything You (N)ever Wanted to Know about Testing View Controllers
Everything You (N)ever Wanted to Know about Testing View ControllersBrian Gesiak
 
Quick: Better Tests via Incremental Setup
Quick: Better Tests via Incremental SetupQuick: Better Tests via Incremental Setup
Quick: Better Tests via Incremental SetupBrian Gesiak
 
Property-Based Testing in Objective-C & Swift with Fox
Property-Based Testing in Objective-C & Swift with FoxProperty-Based Testing in Objective-C & Swift with Fox
Property-Based Testing in Objective-C & Swift with FoxBrian Gesiak
 
Intel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance ProgrammingIntel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance ProgrammingBrian Gesiak
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API DesignBrian Gesiak
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API DesignBrian Gesiak
 
Apple Templates Considered Harmful
Apple Templates Considered HarmfulApple Templates Considered Harmful
Apple Templates Considered HarmfulBrian Gesiak
 
RSpec 3.0: Under the Covers
RSpec 3.0: Under the CoversRSpec 3.0: Under the Covers
RSpec 3.0: Under the CoversBrian Gesiak
 
iOS Behavior-Driven Development
iOS Behavior-Driven DevelopmentiOS Behavior-Driven Development
iOS Behavior-Driven DevelopmentBrian Gesiak
 

Plus de Brian Gesiak (10)

iOS API Design
iOS API DesigniOS API Design
iOS API Design
 
Everything You (N)ever Wanted to Know about Testing View Controllers
Everything You (N)ever Wanted to Know about Testing View ControllersEverything You (N)ever Wanted to Know about Testing View Controllers
Everything You (N)ever Wanted to Know about Testing View Controllers
 
Quick: Better Tests via Incremental Setup
Quick: Better Tests via Incremental SetupQuick: Better Tests via Incremental Setup
Quick: Better Tests via Incremental Setup
 
Property-Based Testing in Objective-C & Swift with Fox
Property-Based Testing in Objective-C & Swift with FoxProperty-Based Testing in Objective-C & Swift with Fox
Property-Based Testing in Objective-C & Swift with Fox
 
Intel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance ProgrammingIntel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance Programming
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API Design
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API Design
 
Apple Templates Considered Harmful
Apple Templates Considered HarmfulApple Templates Considered Harmful
Apple Templates Considered Harmful
 
RSpec 3.0: Under the Covers
RSpec 3.0: Under the CoversRSpec 3.0: Under the Covers
RSpec 3.0: Under the Covers
 
iOS Behavior-Driven Development
iOS Behavior-Driven DevelopmentiOS Behavior-Driven Development
iOS Behavior-Driven Development
 

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