Các bài học lập trình ứng dụng RESTful ~ Chuyên đề phát triển Web - [Chương 11 ] Refactoring ( phía Client)


:warning: Nội dung của bài này khá là ngắn gọn

## :large_blue_circle: Xin chào các bạn

Bài viết này được dịch từ bài [リファクタリング（クライアント編）- AWS上で構築するRESTfulアプリ勉強会～Web開発ワークショップ～【第11回】マニュアル](http://qiita.com/k_shimoji/items/953b2071fcf156c0d595) của tác giả @k_shimoji .


## :large_blue_circle: Refactoring là gì ?

Nhắc lại bài trước một tí, theo định nghĩa thì nó là việc cải thiện cấu trúc bên trong mà không làm thay đổi các hoạt động của chương trình. Hay nói ngắn gọn hơn là dọn dẹp code ( clean code ).

## :large_blue_circle: Các bước Refactoring

1. Xác định nơi cần refactoring.
2. Viết các test để kiểm tra .
3. refactoring
4. test
5. Trở lại bước 1


## :one: Xác định nơi cần refactoring

Có một số "Mùi code - (smell code) " ( ám chỉ code bừa bộn ) ở code của chúng ta. Những đoạn code cần refactoring sẽ có những mùi đặc trưng sau. 
* Quote: [Định nghĩa mùi đáng ngại] (http://objectclub.jp/technicaldoc/refactoring/refact-smell) *

- Lặp code
- method quá dài
- Class quá to
- Quá nhiều arguments
- Quá nhiều sự thay đổi
- Biến thay dổi 
- Các thuộc tính, sự phụ thuộc bất hợp lý của operation
- Header của dữ liệu
- Chưa tuân thủ các kiểu dữ liệu cơ bản.
- Lệnh Switch
- Thừa kế song song 
- Class lười biếng
- Những thành phần được sinh ra đáng nghi
- Thuộc tính tạm thời
- Chuỗi tin nhắn
- Trung gian
- Các mối quan hệ không đúng
- Class interface không phù hợp
- Thư viện quá cũ
- Data class
- Từ chối thừa kế
- Comment

### :point_up: Mình sẽ phân loại các mùi này ra

Có cảm giác là hơi gò bó khi xếp chúng thành các nhóm như này, tuy nhiên nó sẽ giúp mình dễ dàng tiếp cận hơn một tí nên tôi sẽ cố gắng phân loại như sau :smirk:

- Mùi quá rõ ràng
- Mùi xuất hiện khi bạn bắt đầu đọc code
- Cấu trúc rắc rối
- Hệ thống lỗi thời
- Mùi tanh
- Mùi mồ hôi
- Mùi lừa dối

Tôi sẽ giải thích ở phần dưới.
Cho mỗi mùi, tôi sẽ viết một "cách khử mùi" và "những nguyên tắc nó vi phạm", còn phần giải thích thì ở bài viết này chắc là không đủ, vì vậy mong các bạn tìm kiếm thêm thông tin về nó trên google nếu các bạn thực sự hứng thú.

#### Mùi quá rõ ràng

Không cần làm gì cũng biết code có mùi, nhìn qua chứ không cần đọc code luôn.

- 2. method quá dài
   - Hãy tách nó ra.
- 3. class quá lớn
   - Tách nó ra.
- 4. Quá nhiều arguments
   - Hãy phân loại các object rõ ràng hơn.
- 10. Quá nhiều lệnh switch
   - Hãy sử dụng hướng đối tượng.



#### Mùi xuất hiện khi bạn bắt đầu đọc code

Những mùi này ngay lập tức xuất hiện khi bạn đọc qua các tiêu đề

- 1. Code bị lặp
   - Vi phạm nguyên tắc DRY, hãy cố dừng việc copy paste lại.
- 12. Class lười biếng
   - Một class nhỏ tồn tại mà không có nhiều ý nghĩa với những gì bạn đang làm, tốt nhất là tạo một role ở class khác chứ không cần để riêng ra.
- 16. Trung gian
   - Một số Class không thực hiện nhiệm vụ của chính nó, xóa nó đi hoặc cho nó làm thêm các thao tác khác.
- 18. Interface Class không phù hợp
   - Ai đó đã làm một việc tương tự ? hãy ngầm hiểu với nhau.
- 20. Data class
   - Hay còn gọi là "Thiếu domain model". Hãy nhớ data và các hành vi.
   
#### Cấu trúc rắc rối

- 5. Quá nhiều sự thay đổi
   - Vi phạm nguyên tắc SRP (single responsibility principle). Một class đã làm việc quá tải, hay chia sẻ công việc của nó.
- 6. Biến bị thay đổi
   - Chia sẻ 1 công việc cho quá nhiều class. Hãy phân chia trách nhiệm rõ ràng hơn .
- 11. Thừa kế song song
   - Một ví dụ điểm hình của vi phạm nguyên tắc "Những ựu thay đổi bị phân tán." OCP (open / closed principle). Hãy chia sẻ đúng công việc của class cha và class con. 

#### Hệ thống lỗi thời

Ở thời điểm trước ( phụ thuộc vào hệt thống thời đó hoặc thời gian phát triển mà nó có khi chỉ là sự thay đổi rất nhỏ) nó có thể không phải mùi, tuy nhiên nó khiến cho việc bảo trì trở nên khó khăn khi công nghệ đó đã quán cũ

- 7. thuộc tính, các sự gắn kết không hợp lệ của các hoạt động
   - Các luật Roles của class trở nên không rõ ràng, hãy sửa roles cho thích hợp
- 15. Chuối tin nhắn
   - Một object được biết từ việc nhận của một object từ một object khác...Hãy tổ chức lại chúng vì rất phức tạp.
- 17. Các mối quan hệ không đúng cách
   - Nếu thấy các class có mối liên hệ quá chặt chẽ, hãy để nó là chính nó.
- 21. Bị từ chối thừa kế
   - Bạn đã thừa kế từ một vài class nhưng  bạn lại overriding quá nhiều nên class cha không có ý nghĩa gì cả. (Nó bỗng trở thành một loại khác), hãy dừng việc thừa kế lại hoặc lấy method từ class cha để dùng cho class con.

#### Mùi cá

Mình tạm gọi vui cái mùi này giống như mùi cá tanh.
Nó thiếu data modeling, thiếu sự cân nhắc trước khi đóng gói. Hãy cẩn thận hơn dù quá một chút thời gian.

- 8. Data gửi vào header
   - Data cần luôn được sử sử dụng ở cùng một set, hãy cố gắng để nó thật sát với object.
- 9. Tuân thủ các kiểu dữ liệu cơ bản
   - Hãy tạo một ValueObject
- 14. Thuộc tính tạm thời
   - Các biến (field) không được sử dụng hoặc đã sử dụng xong. Hãy chuyển nó thành các biến ngoài hoặc NullObject 

#### Mùi mồ hôi

Bạn đã làm việc rất vất vả, tuy nhiên code của bạn vẫn rất tồi, và thứ duy nhất có thể xuất ra là mồ hôi.

- 13. Nghi ngờ quá nhiều
   - Bạn đã code mà nghĩ quá nhiều đến những thứ tương lai cần dùng đến, vì vậy bạn đã implement rất nhiều thứ không cần thiết tại thời điểm hiện tại. Hãy chỉ implement nếu bạn thực sự cần nó, chừng nào bạn vẫn chưa cần nó, hãy cố gắng để chương trình của bạn đơn giản nhất có thể.
   
#### Mùi lừa dối

Mặc dù code không cố tình viết sai, nhưng kết quả nó lại sai.

- 19. Không sử dụng được class thư viện
   - Thư viện không thể sử dụng như chính khả năng của nó, hãy sửa lại để có thể sử dụng được, đừng phó mặc tất cả cho thư viện.
- 22. Comments
   - Comments quá cũ hoặc sai sự thật. Hãy cố đọc hiểu chương trình càng nhiều càng tốt mà không chú ý đến comment. Nó có thể không chú thích gì cho code cả ( tại sao lại dùng logic này , vv...) chỉ với mục tiêu là bạn muốn comment, hãy cố gắng viết code thật dễ hiểu mà không cần tóm tắt nó bằng comment.


## :two: Bạn đã có test nào chưa? Nếu chưa, hãy viết test ngay - PhpUnit

Refactoring is nghĩa là "không thay đổi hoạt động của chương trình" nhưng "cải thiện được cấu trúc bên trong".
Để chắc chắn rằng những hành vi của chương trình không thay đổi. bạn không thể chỉ test một lần

Hãy bắt đầu test nào.

Chú ý rằng với việc viết test thì có một điều rất quan trọng là **"Test phải dễ dàng để viết"**.

## :three: Refactoring

Có một vài kỹ thuật cho mỗi "code smell" ( mùi của code ), tuy nhiên tôi không thể mô tả hết được, ở workshop này mình chỉ giữ mọi thứ thật đơn giản.

Bởi vì hàn Upload ở bài trước có mùi của "method quá dài", hãy thử refactor nó bằng cách áp dụng cách "Chia Method".

## :four: Test

Hãy kiểm tra kết quả bằng cách test sau khi đã hoàn tất refactoring.
Để viết một test code tốt,
**Điều quan trọng là những gì bạn muốn test phải được phân biệt rõ ràng** Nếu bạn không muốn test tất cả mọi thứ.

Các test phải được implement như là một "giá trị giả", mình sẽ tiếp tục test hiệu quả chương trình.

## :five: Trở lại bước 1

Refactoring cần luôn được thực thi.
Hãy tiếp tục làm giảm các gánh nặng kỹ thuật cho chương trình.



## :large_blue_circle: Mục lục bài học

- Chuẩn bị
   - Duy trì Branch
- Bài 1: Test hiển thị link Delete, Detail, nút checkbox hoàn tất của TODO List.
- Bài 2: Test hiển thị data trong trang TODO detail
- Bài 3: Refactoring một chút về việc hiển thị các data.


## :large_blue_circle: Chuẩn bị

Phần chuẩn bị sẽ giống nhau ở mỗi bài, vì vậy tôi đã tổng hợp nó ở một entry riêng. Các bạn vui lòng xem ở link này [All 12 times of study sessions in do I have use of Git - RESTful application workshops will be built on AWS ~ Web development workshop ~ - Qiita](http://qiita.com/k_shimoji/items/6a66dbb93b0553fa7c7f) để chuẩn bị .

- Git: Bây giờ mình sẽ tạo branch `vol/10` và làm việc trên nó.

OK, bước đầu đã xong

:warning: *** Tôi xin nhắc lại ở bài 5 và bài 6, chúng ta đã có những table bị thay đổi. ***

- Bài 5
  - Tạo Table dành cho việc đăng ký thành viên ( Thực hiện cho chức năng login )
- Bài 6
  - Thêm một cột cho table TODO list ( Thêm cột Owner và cột assignee cho chức năng phân công công việc)

Các bạn nên tham khảo lại các link sau để rõ hơn.
[Tạo Table dành cho việc đăng ký thành viên](http://qiita.com/Takumi_Kasuga/items/c97e96fb91b83ed68782#t%E1%BA%A1o-table-cho-vi%E1%BB%87c-%C4%91%C4%83ng-k%C3%BD-user)
[Thêm một cột cho table TODO list](http://qiita.com/Takumi_Kasuga/items/6d7506f8dc7de511bf14#large_blue_circle-chu%E1%BA%A9n-b%E1%BB%8B)


Khi mọi thứ đã xong xuôi, chúng ta bắt đầu bài 1 nào.

## :large_blue_circle: Bài 1: Test hiển thị link Delete, Detail, nút checkbox hoàn tất của TODO List.

Bởi vì trong logic có một chút điều kiện có vấn đề ở client side, nên mình sẽ thử test tại đây. Checkbox và các button hiển thị như phần mũi tên trong hình ảnh dưới đây là dành cho trường hợp hiển thị cho nội dung của chính người tạo todo.

![todo.png](https://qiita-image-store.s3.amazonaws.com/0/65988/55d7fc67-0967-2a98-eae6-574012114b5c.png "todo.png")

Chi tiết phân quyền


| | Được phân công | Không được phân công |
| --- |:---------------------- |:--------------------|
| Owner | Delete = hiện, details = hiện, Check = Được phép | Delete = hiện, detail = hiện, Check = được phép |
| Không phải owner | Delete = ẩn, detail = hiện, Check = Được phép | Delete = ẩn, detail = ẩn, Check = Không được phép |


### Tool để test

#### mocha

Đây là một test framework giúp bạn chọn và chạy các test.
Nó yêu cầu một Tool để xác nhận, mình sẽ sử dụng công cụ `chai`.

#### chai 

Đây là công cụ dùng để xác nhận kết quả test.
Mặc định của nó ở định dạng BDD, tuy nhiên bạn có thể chọn một ký hiệu định dạng TDD, ở bài học này mình sẽ viết test ở BDD method.

#### sinon

Tool này dùng để tạo ra các mock hoặc stub cho việc test.

#### Cài đặt

Chạy các lệnh theo hướng dẫn dưới đây là OK

```bash:Command
#lib
cd /var/www/study/rest-study/app/webroot/js/lib
wget https://raw.githubusercontent.com/mochajs/mocha/master/mocha.js
wget https://github.com/chaijs/chai/raw/master/chai.js
wget http://sinonjs.org/releases/sinon-1.17.2.js
#css
cd /var/www/study/rest-study/app/webroot/css
wget https://github.com/mochajs/mocha/raw/master/mocha.css
```

Vậy là mình đã chuẩn bị xong.

### Danh sách file cần sửa

- app/webroot/clientTest.html
	- Nó sẽ hiển thị kết quả thực hiện sau mỗi lần chạy test.
- app/webroot/js/test/test-require-config.js
	- Tạo require cho việc test
- app/webroot/js/test/setup.js
	- Đọc test, thiết đặt các tool
- app/webroot/js/test/tests.js
	- Chạy test
- app/webroot/js/test/test-todo-item-view.js
	- viết test code
- app/webroot/js/test/test-login.js
	- Test các vấn đề liên quan đến đăng nhập

#### app/webroot/clientTest.html

```html
<html>
  <head>
	<meta charset="utf-8"/>
	<title>TodoList Tests</title>
	<link rel="stylesheet" href="css/mocha.css"/>
  </head>
  <body>
	<div id="mocha"></div>
	<script type="text/javascript" src="js/require-config.js"></script>
	<script type="text/javascript" src="js/test/test-require-config.js"></script>
	<script type="text/javascript" src="js/lib/require.js" data-main="test/setup.js"></script>
  </body>
</html>
```

- `require-config.js`
	- File Cấu hình của require.js sẽ sử dụng trong cả chương trình. Giải quyết sự phụ thuộc.
- `test-require-config.js`
	- Đây cũng là thiết lập của require.js. Bạn sẽ phải thêm thiết lập cho test.
- `data-main="test/setup.js"`
	- Xác định các hoạt động của js

:warning: Phần này gần giống như `default.ctp` của todo app. 


#### app/webroot/js/test/test-require-config.js

```js
require.paths.mocha = 'lib/mocha';
require.paths.chai = 'lib/chai';
require.paths.sinon = 'lib/sinon-1.17.2';
require.shim.mocha = {
	init: function () {
		this.mocha.setup('bdd');
		return this.mocha;
	}
};
```

- `require.paths.mocha = 'lib/mocha';`
	- Chúng ta sẽ thiết lập path của mocha. Vì  `app/webroot/js/require-config.js` đã có sẵn thiết lập `require.paths` rồi nên mình chỉ cần viết vậy là sẽ thêm được mocha vào.
	- chai, sinon sẽ được thiết lập chung paths.


#### app/webroot/js/test/setup.js

```js
require([
	'mocha',
	'chai',
	'marionette',
], function (mocha, chai) {
	require([
		'js/test/tests.js'
	], function () {
		chai.should();
		mocha.run();
	});
});
```

mocha, chai đã được đọc (require), bây giờ chúng ta sẽ đọc file js/test/tests.js (test code) với các nested require, mình sẽ khởi tao để chạy test code sau khi chạy hàm  `mocha.run()`.

#### app/webroot/js/test/tests.js

```js:app/webroot/js/test/tests.js
define(function (require) {
	//doc file
	var testTodoItemView = require('js/test/test-todo-item-view.js');
	//chay test
	testTodoItemView();
});
```

Chúng ta sẽ chạy test. Vì test thực sự đã được mô tả ở `js/test/test-todo-item-view.js`, hãy viết nó theo những gì bạn muốn chạy bằng require.
Sau khi tăng số lượng test module, mình sẽ viết test code để thêm một dòng "run" trong require và hàm test để chạy ở file này.

#### app/webroot/js/test/test-todo-item-view.js

```js:app/webroot/js/test/test-todo-item-view.js
define(function(require){
	var TodoItemView = require('views/todo-item-view');
	var TodoModel = require('models/todo-model');
	var loginTest = require('test/test-login');
	return function(){
		describe("TODO一覧表示の削除リンク、詳細リンク、完了チェックボックスの表示テスト", function () {
			describe("ログインチェック", function () {
				it('ログインチェック', function (done) {
					loginTest(done);
				});
			});

			describe("オーナかつ担当者の場合", function () {
				it("削除リンク O / 詳細リンク O / チェック O", function () {
					itemView = _createAndRenderTodoItemView(true, true);
					//削除リンクが表示されている
					itemView.ui.removeLink.css('display').should.be.equals('');
					//詳細リンク表示されている
					itemView.ui.detailLink.css('display').should.be.equals('');
					//完了チェックボックスが押せる
					itemView.ui.checkBox.prop('disabled').should.be.equals(false);
				});
			});

			describe("オーナだが担当者ではない場合", function () {
				it("削除リンク O / 詳細リンク O / チェック O", function () {
					var itemView = _createAndRenderTodoItemView(true, false);
					//削除リンクが表示されている
					itemView.ui.removeLink.css('display').should.be.equals('');
					//詳細リンク表示されている
					itemView.ui.detailLink.css('display').should.be.equals('');
					//完了チェックボックスが押せる
					itemView.ui.checkBox.prop('disabled').should.be.equals(false);
				});
			});

			describe("オーナではないが担当者の場合", function () {
				it("削除リンク X / 詳細リンク O / チェック O", function () {
					var itemView = _createAndRenderTodoItemView(false, true);
					//削除リンクが非表示(display=none)になっている
					itemView.ui.removeLink.css('display').should.be.equals('none');
					//詳細リンク表示されている
					itemView.ui.detailLink.css('display').should.be.equals('');
					//完了チェックボックスが押せる
					itemView.ui.checkBox.prop('disabled').should.be.equals(false);
				});
			});
			describe("オーナでもなく担当者でもない場合", function () {
				it("削除リンク X / 詳細リンク X / チェック X", function () {
					var itemView = _createAndRenderTodoItemView(false, false);
					//削除リンクが非表示(display=none)になっている
					itemView.ui.removeLink.css('display').should.be.equals('none');
					//詳細リンクが非表示(display=none)になっている
					itemView.ui.detailLink.css('display').should.be.equals('none');
					//完了チェックボックスが押せない(disabled=true)
					itemView.ui.checkBox.prop('disabled').should.be.equals(true);
				});
			});
			//utility
			function _createAndRenderTodoItemView(owned, assigned){
				//サーバから受信するデータ
				data = {
					TodoList: {
						id: "1",
						todo: "do somothing",
						status: "0",
						owned: owned,
						assigned: assigned
					},
					Owner: {
						id: "1",
						name: "anonymous"
					},
					Assignee: {
						id: "1",
						name: "anonymous"
					}
				};
				//サーバからのデータをparseしてmodel生成
				var model = new TodoModel(data, {parse: true});
				//テンプレート。テスト対象のみ
				var template =
					'<div>' +
					'  <a class="remove-link">削除</a>' +
					'  <a class="detail-link">詳細</a>' +
					'  <input type="checkbox" class="toggle"></input>' +
					'</div>';
				//modelとtemplateを渡してviewを生成
				var view = new TodoItemView({
					model: model,
					//template : $(template)
				});
				//表示までしてからreturn
				view.template = $(template);
				view.render();

				return view;
			}
		});
	}
});
```

Đây là test code chính. Những gì chúng ta đang thực hiện có thể tóm tắt như sau,

- Đăng nhập
- Đọc file `views/todo-item-view` để hiển thị giao diện
- Chạy 4 code block ( sau đây là các điều kiện của testcase tương ứng ) bắt đầu với `describe`.
	- Trường hợp là owner hoặc assignee.
        - Trường hợp không phải owner nhưng là assignee.
        - Không phải owner hay asssignee
        - Nếu không phải owner hay Assignee

Chi tiết từng test code

- Gồm các cú pháp bắt đầu bằng `describe` trong test code. 
	- Miêu tả đối tượng cần test bằng `describe`
	- Ở `it` bạn sẽ viết code để thực hiện test code để xác nhận kết quả. Đây là phần body của the test.
		- `it mình có thể viết vào (test) nên là ~`
		- Tại đây, Model cho việc hiển thị TODO, bằng cách thay đổi giá trị của `owned` và `assigned` để hiển thị view, delete button, detail button, ngoài ra còn test xem chúng ta không thể hay có thể check vào checkbox.
	- Để sinh ra view tôi đã thực hiện ở hàm `_createAndRenderTodoItemView`.
		- Giá trị sẽ được set cho `owned` và `assignee`, các argument bắt đầu được xác định.
		- Template tại thời điểm sinh ra view để thiết lập các thứ trong DOM, hiển thị delete button, detail button, có thể set checkbox ở DOM template.


#### app/webroot/js/test/test-login.js

```js:app/webroot/js/test/test-login.js define(function (require) {
	var UserModel = require('models/user-model');

	var userModel = new UserModel();
	return function login(done){
		if (!userModel.isLoggedIn()) {
			userModel.login(
				'yamada',   //DB登録データに合わせる
				'test',		//DB登録データに合わせる
				function success(message) {
					console.info("logged in!")
					done();
				},
				function error(message) {
					done('login error! ' + '\n' + message);
				}
			)
		} else {
			//ログイン済みなら終了
			console.info('ログイン済み');
			done();
		}
	}
});
```

- Chạy login API để đăng nhập vào server.
   - Xác định User name, password, set theo điều kiện bạn cần test.
   - Sau khi đăng nhập thành công, chạy hàm done() để kết thúc test.

:warning: về hàm `done`

Bằng việc chạy hàm done trong hàm `it`, bạn có thể kết thúc việc thực thi test.
Bản thân hàm `it` cũng sẽ bị bỏ qua nếu đang chạy `userModel.login` tuy nhiên, hàm done được thực hiện sau khi hàm `success` được thực thi của hàm callback được xác định trong `userModel.login`.
Vì hàm test khi kết thúc đã tự động chạy hàm done , test sẽ được chạy tiếp cho đến khi nào bạn call hàm asynchronous callback function bằng cơ chế này.

Nó giống những gì mình đã thực hiện ở file `app/webroot/js/test/test-todo-item-view.js` trước đó ,

```js:app/webroot/js/test/test-todo-item-view.js

〜省略〜
		describe("TODO一覧表示の削除リンク、詳細リンク、完了チェックボックスの表示テスト", function () {
			describe("ログインチェック", function () {
				it('ログインチェック', function (done) {
					loginTest(done);
				});
			});
〜省略〜
```

Giống như chúng ta đang chạy hàm logintTest. Truyền argument `done` của nó vào hàm loginTest, hãy tạo flow sao cho hàm done chỉ thực hiện nội bộ.



### Chạy test

Truy cập link
http://(PublicIp)/rest-study/clientTest.html

:warning: Các kết của của bài này sẽ được show như dưới đây.

![test.png](https://qiita-image-store.s3.amazonaws.com/0/65988/cd5302c5-dec7-eb10-1bc3-7989af25946c.png "test.png")

Khi mọi thứ đã OK, hãy commit lên Git.

**:warning: Hiển thị dạng Diff trên GitHub**


[第11回 Lesson1 TODO一覧表示の削除リンク、詳細リンク、完了チェックボックスの表示テスト · suzukishouten-study/rest-study@728ff09](https://github.com/suzukishouten-study/rest-study/commit/728ff099c69f288728e86b939001a7791bfa1a4b)

Sang bài 2 nào !


## :large_blue_circle: Bài 2: Test hiển thị data trong trang TODO detail

Tôi đã nói rằng code này có các mùi ở javascript, nó sẽ dẫn đến các " địa ngục callback" . Để refactor các phần sử dụng callback, đầu tiên là viết test ở bài 2 này.

- Event được điều khiển bởi `ListenTo` - Sẽ thay đổi thành điều khiển bởi hàm callback > success 
- Mình muốn nó thực hiện việc chuyển sang màn hình hiển thị khác khi đang thu thập TODO list hoặc userlist cả 2 đều đã hoàn tất.

Hoàn tất bài 2.
Logo hiển thị của màn hình TODO detail hiện tại đang sử dụng như sau.

```js:app/webroot/js/views/todo-detail-layout-view.js
//詳細画面用レイアウトビュー
define(function(require) {
    var TodoDetailItemView = require('views/todo-detail-item-view');
    var TodoModel = require('models/todo-model');
    var UserCollection = require('collections/user-collection');

	var TodoDetailLayoutView = Marionette.LayoutView.extend({
		//テンプレート
		template : '#todo-detail-layout-template',

		regions : {
			itemRegion : '#todo-item',
		},

		onRender : function() {
			this.userCollection = new UserCollection();
			this.listenToOnce(this.userCollection, 'reset', this.onLoadUsers, this);
			this.userCollection.fetch({
				reset : true
			});
		},

		onLoadUsers : function(userCollection){
 			var todoModel = new TodoModel({
 				id : this.options.modelId
 			});
 			//モデルのサーバからのデータ取得完了時、描画を行う
			this.listenToOnce(todoModel, 'sync', this.showItem, this);
 			//サーバからデータ取得
 			todoModel.fetch({
 				wait : true
 			});
 		},

 		showItem : function(todoModel) {
 			this.itemRegion.show(new TodoDetailItemView({
 				model : todoModel,
 				userList : this.userCollection.models
 			}));
 		},

	});
	return TodoDetailLayoutView;
});
```

1. Lấy user list bằng hàm `onRender`, sau khi nhận phản hồi thành công từ server, chạy hàm `onLoadUsers`.
2. Ở hàm `onLoadUsers` để lấy danh sách TOTO, sau khi nhận phản hồi thành công từ server mình chạy hàm ` showItem`.
3. Ở hàm `shoItem`, mình sẽ sinh ra và hiển thị theo ItemView.

Và nó sẽ như sau đây, nhưng đây chỉ là sau bài 1 và bài 2, phần handling sẽ ở bài 3.
Ở giai đoạn trước trong bài 2, sự thật là "bài 3 thì hàm `showItem` sẽ được thực thi" sẽ hoàn tất, mình đã đủ cơ sở để test.

### Danh sách các file cần chỉnh sửa

- app/webroot/js/test/test-todo-detail-layout-view.js
	- test code
- app/webroot/js/test/tests.js
	- chạy test

#### app/webroot/js/test/test-todo-detail-layout-view.js

```js:app/webroot/js/test/test-todo-detail-layout-view.js
define(function(require){
	var sinon = require('sinon');
	var TodoDetailLayoutView = require('views/todo-detail-layout-view');
	var TodoModel = require('models/todo-model');
	var loginTest = require('test/test-login');
	return function () {
		describe("TODO詳細の表示データ取得テスト", function () {
			it('ログインチェック', function (done) {
				loginTest(done);
			});

			it("Todoとユーザ一覧取得", function (done) {
				var layoutView = _createTodoDetailLayoutView();
				sinon.stub(layoutView, 'showItem', function (todoModel) {
					todoModel.should.be.ok;
					layoutView.showItem.restore();
					done();
				});
				layoutView.render();
			});

			//utility
			function _createTodoDetailLayoutView(data) {
				//テンプレート
				var template = '<div><div></div></div>';
				//modelとtemplateを渡してviewを生成
				var view = new TodoDetailLayoutView({
					template: $(template),
					modelId: 1
				});
				return view;
			}
		});
	}
});
```

Sử dụng `sinon.stub`, mình sẽ tạo ra stub cho hàm `showItem`.
Ở stub, cơ bản bởi vì mình chỉ check mỗi những stub được thực thi, argument sẽ là mọi thứ chỉ OK nếu mình check không có giá trị null.

#### app/webroot/js/test/tests.js

```diff:app/webroot/js/test/tests.js
 define(function (require) {
 	//読み込み
 	var testTodoItemView = require('js/test/test-todo-item-view.js');
+	var testTodoDetailLayoutView = require('js/test/test-todo-detail-layout-view.js');
 	//実行
 	testTodoItemView();
+	testTodoDetailLayoutView();
 });
```

Tạo mới file `test-todo-detail-layout-view.js` để thực hiện test.


Khi đã thêm test, hãy chạy.

Nếu mọi thứ đã ổn, hãy commit lên Git.
**:warning: Hiển thị dạng Diff trên GitHub**

[第11回 Lesson2 TODO詳細の表示データ取得テスト · suzukishouten-study/rest-study@5c7b38a](https://github.com/suzukishouten-study/rest-study/commit/5c7b38a5142c73d130f74a96358d1067aa5525ad)
Lesson3へ!

## :large_blue_circle: Bài 3+ Refactor một chút về việc hiển thị data detail

Khi testcode được thêm vào, mình sẽ refactor ở Bài 3.
Sau khi chỉnh sửa, hãy chạy test một lần nữa.

### Danh sách file cần chỉnh sửa

- app/webroot/js/views/todo-detail-layout-view.js
	- Refactoring code của các đối tượng

#### app/webroot/js/views/todo-detail-layout-view.js

```
//詳細画面用レイアウトビュー
define(function(require) {
	var TodoDetailItemView = require('views/todo-detail-item-view');
	var TodoModel = require('models/todo-model');
	var UserCollection = require('collections/user-collection');

	var TodoDetailLayoutView = Marionette.LayoutView.extend({
		//テンプレート
		template : '#todo-detail-layout-template',

		regions : {
			itemRegion : '#todo-item',
		},

		onRender: function () {
			//Todoを取得
 			this.todoModel = new TodoModel({
 				id : this.options.modelId
 			});
 			var todoFetching = this.todoModel.fetch();
			//ユーザ一覧取得
			var userCollection = new UserCollection();
			var userFetching = userCollection.fetch();
			$.when(
				todoFetching,
				userFetching
			).done(function(){
				this.showItem(this.todoModel, userCollection);
			}.bind(this));
		},

		showItem: function (todoModel, userCollection) {
 			this.itemRegion.show(new TodoDetailItemView({
 				model : todoModel,
 				userList : userCollection.models
 			}));
 		},

	});
	return TodoDetailLayoutView;
});
```

Như đã mô tả ở bài 2, flow của mình như sau.

1. Lấy user list bằng hàm `onRender`, sau khi nhận phản hồi thành công từ server, chạy hàm `onLoadUsers`.
2. Ở hàm `onLoadUsers` để lấy danh sách TOTO, sau khi nhận phản hồi thành công từ server mình chạy hàm ` showItem`.
3. Ở hàm `shoItem`, mình sẽ sinh ra và hiển thị theo ItemView.

Bây giờ,

1. Ở hàm `onRender`, lấy user list. Trự tiếp lấy TOTO list trước khi có phản hồi từ server.
2. mình dùng `when` trong jQuery, sử dụng cơ chế của `done` để chạy hàm `showItem` tại thời điểm mà như đã nói ở bài 1 "Lấy toàn bộ danh sách TODO list và users list đã hoàn thành cả 2" .

Flow đã được thay đổi như trên.

:warning: Tôi đã viết「`when`,`done` của jQuery」, chính xác hơn là tôi đã sử dụng những thứ được gọi là `jQuery.Deferred`.
Định nghĩa chi tiết ở dưới đây, bài viết khá hay nên tôi dẫn link trực tiếp luôn
[爆速でわかるjQuery.Deferred超入門 - Yahoo! JAPAN Tech Blog](http://techblog.yahoo.co.jp/programming/jquery-deferred/)

:warning: `UserCollection` lúc đầu mình viết với `this.userCollection`, bởi vì chỉ sử dụng như biến cục bộ là đủ nên mình sẽ đổi nó thành biến cục bộ.
Vì vậy, mình cần truyền `userCollection` của nơi mà nhận data vào hàm showItem.
Vì lý do này, bạn cần sửa hàm `showItem` của các stub của test code, các chỉnh sửa như sau.

#### app/webroot/js/views/todo-detail-layout-view.js


```diff:app/webroot/js/views/todo-detail-layout-view.js
 
 			it("Todoとユーザ一覧取得", function (done) {
 				var layoutView = _createTodoDetailLayoutView();
-				sinon.stub(layoutView, 'showItem', function (todoModel) {
+				sinon.stub(layoutView, 'showItem', function (todoModel, userCollection) {
 					todoModel.should.be.ok;
+					userCollection.should.be.ok;
 					layoutView.showItem.restore();
 					done();
 				});
```

Thêm argument `userCollection`, chúng ta phải kiểm tra rằng nó không phải null.

Mọi chỉnh sửa đã xong.

Hãy chạy test.

Khi mọi thứ đã ổn, hãy commit lên Git.

**:warning: Hiển thị dạng diff trên Github**

[第11回 Lesson3 TODO詳細の表示データ取得処理のちょっとリファクタリング · suzukishouten-study/rest-study@a7dd0af](https://github.com/suzukishouten-study/rest-study/commit/a7dd0af28153c71ec48b8833df477a85d06469f3)



## Trailer

Bài tiếp theo sẽ là bài cuối cùng của series.
Sẽ tốt hơn nếu các bạn cùng làm việc nhóm.

## Lời cảm ơn

Cảm ơn các bạn đã theo dõi bài viết này, tôi rất hi vọng nhận được những bình luận cũng như phản hồi của tất cả các bạn để làm tốt hơn. Chương 11 tạm kết thúc tại đây.