├── LICENSE └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Leonardo Bernardes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Original Repository: [ryanmcdermott/clean-code-java](https://github.com/leonardolemie/clean-code-java) 3 | 4 | # clean-code-java 5 | 6 | ## Mục lục 7 | 1. [Giới thiệu](#giới-thiệu) 8 | 2. [Biến](#biến) 9 | 3. [Hàm](#hàm) 10 | 4. [Đối tượng và Cấu trúc dữ liệu](#Đối-tượng-và-cấu-trúc-dữ-liệu) 11 | 5. [Lớp](#lớp) 12 | 6. [SOLID](#solid) 13 | 7. [Testing](#testing) 14 | 8. [Xử lí đồng thời](#xử-lí-đồng-thời) 15 | 9. [Xử lí lỗi](#xử-lí-lỗi) 16 | 10. [Định dạng](#Định-dạng) 17 | 11. [Viết chú thích](#viết-chú-thích) 18 | 12. [Các ngôn ngữ khác](#các-ngôn-ngữ-khác) 19 | 20 | ## Giới thiệu 21 | ![Humorous image of software quality estimation as a count of how many expletives 22 | you shout when reading code](http://www.osnews.com/images/comics/wtfm.jpg) 23 | 24 | Những nguyên tắc kỹ thuật phần mềm, từ cuốn sách 25 | [*Clean Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882), 26 | áp dụng cho ngôn ngữ Java. Đây không phải là một hướng dẫn về cách viết code Java mà là hướng dẫn về cách viết các đoạn code dễ đọc hiểu, tái sử dụng và tái cấu trúc được trong Java. 27 | 28 | Không phải mọi nguyên tắc ở đây phải được tuân thủ một cách nghiêm ngặt, 29 | và thậm chí chỉ có một ít trong số đó được sử dụng phổ biến. Ở đây, nó chỉ là một 30 | hướng dẫn - không hơn không kém, nhưng chúng được hệ thống hóa thông qua kinh 31 | nghiệm thu thập được qua nhiều năm của các tác giả của cuốn sách *Clean Code* 32 | 33 | Ngành kỹ thuật phần mềm chỉ phát triển được hơn 50 năm, và chúng ta vẫn 34 | đang học rất nhiều. Một khi kiến trúc phần mềm trở thành phổ biến, có lẽ sau đó 35 | chúng ta sẽ có thêm nhiều luật lệ khó hơn phải tuân theo. Còn giờ đây, 36 | hãy để những hướng dẫn này như là một tiêu chuẩn để đánh giá chất lượng các đoạn 37 | code Java mà bạn và team của bạn tạo ra. 38 | 39 | Biết những hướng dẫn này thôi sẽ không thể ngay lập tức làm bạn trở thành một 40 | lập trình viên phần mềm tốt hơn được, và làm việc với chúng trong nhiều năm 41 | cũng không có nghĩa bạn sẽ không gặp bất cứ sai lầm nào. Mỗi đoạn code bắt đầu 42 | như một bản thảo đầu tiên, giống như đất sét được nặn nhào và cho tới cuối cùng 43 | thì nó sẽ lộ diện hình hài. Cuối cùng, chúng ta gọt tỉa những khuyết điểm khi 44 | chúng ta xem xét lại nó cùng với các đồng nghiệp. 45 | Đừng để bản thân bạn bị đánh bại bởi những bản thảo đầu tiên, 46 | thứ mà vẫn cần phải được chỉnh sửa. Thay vào đó hãy đánh bại những dòng code. 47 | 48 | ## **Biến** 49 | ### Sử dụng tên biến có nghĩa và dễ phát âm 50 | 51 | **Không tốt:** 52 | ```java 53 | String yyyymmdstr = new SimpleDateFormat("YYYY/MM/DD").format(new Date()); 54 | ``` 55 | 56 | **Tốt:** 57 | ```java 58 | String currentDate = new SimpleDateFormat("YYYY/MM/DD").format(new Date()); 59 | ``` 60 | **[⬆ về đầu trang](#mục-lục)** 61 | 62 | ### Sử dụng cùng từ vựng cho cùng loại biến 63 | 64 | **Không tốt:** 65 | ```java 66 | getUserInfo(); 67 | getClientData(); 68 | getCustomerRecord(); 69 | ``` 70 | 71 | **Tốt:** 72 | ```java 73 | getUser(); 74 | ``` 75 | 76 | 77 | **[⬆ về đầu trang](#mục-lục)** 78 | 79 | ### Sử dụng các tên có thể tìm kiếm được 80 | Chúng ta sẽ đọc code nhiều hơn là viết chúng. Điều quan trọng là code chúng ta 81 | viết có thể đọc được và tìm kiếm được. Việc đặt tên các biến *không* có ngữ 82 | nghĩa so với chương trình, chúng ta có thể sẽ làm người đọc code bị tổn thương 83 | tinh thần. 84 | Hãy làm cho các tên biến của bạn có thể tìm kiếm được. 85 | 86 | **Không tốt:** 87 | ```java 88 | // 86400000 là cái quái gì thế? 89 | setTimeout(blastOff, 86400000); 90 | 91 | ``` 92 | 93 | **Tốt:** 94 | ```java 95 | // Khai báo chúng như một biến hằng global. 96 | public static final int MILLISECONDS_IN_A_DAY = 86400000; 97 | 98 | setTimeout(blastOff, MILLISECONDS_IN_A_DAY); 99 | 100 | ``` 101 | **[⬆ về đầu trang](#mục-lục)** 102 | 103 | ### Sử dụng những biến có thể giải thích được 104 | **Không tốt:** 105 | ```javascript 106 | const address = 'One Infinite Loop, Cupertino 95014'; 107 | const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; 108 | saveCityZipCode(address.match(cityZipCodeRegex)[1], address.match(cityZipCodeRegex)[2]); 109 | ``` 110 | 111 | **Tốt:** 112 | ```javascript 113 | const address = 'One Infinite Loop, Cupertino 95014'; 114 | const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; 115 | const [, city, zipCode] = address.match(cityZipCodeRegex) || []; 116 | saveCityZipCode(city, zipCode); 117 | ``` 118 | 119 | **[⬆ về đầu trang](#mục-lục)** 120 | 121 | ### Tránh hại não người khác 122 | Tường minh thì tốt hơn là ẩn. 123 | 124 | **Không tốt:** 125 | ```java 126 | String [] l = {"Austin", "New York", "San Francisco"}; 127 | 128 | for (int i = 0; i < l.length; i++) { 129 | String li = l[i]; 130 | doStuff(); 131 | doSomeOtherStuff(); 132 | // ... 133 | // ... 134 | // ... 135 | // Khoan, `l` là cái gì vậy? 136 | dispatch(li); 137 | } 138 | ``` 139 | 140 | **Tốt:** 141 | 142 | ```java 143 | String[] locations = {"Austin", "New York", "San Francisco"}; 144 | 145 | for (String location : locations) { 146 | doStuff(); 147 | doSomeOtherStuff(); 148 | // ... 149 | // ... 150 | // ... 151 | dispatch(location); 152 | } 153 | ``` 154 | **[⬆ về đầu trang](#mục-lục)** 155 | 156 | ### Đừng thêm những ngữ cảnh không cần thiết 157 | Nếu tên của lớp hay đối tượng của bạn đã nói lên điều gì đó rồi, đừng lặp lại điều đó trong tên biến nữa. 158 | 159 | **Không tốt:** 160 | ```java 161 | class Car { 162 | public String carMake = "Honda"; 163 | public String carModel = "Accord"; 164 | public String carColor = "Blue"; 165 | } 166 | 167 | void paintCar(Car car) { 168 | car.carColor = "Red"; 169 | } 170 | ``` 171 | 172 | **Tốt:** 173 | ```java 174 | class Car { 175 | public String make = "Honda"; 176 | public String model = "Accord"; 177 | public String color = "Blue"; 178 | } 179 | 180 | void paintCar(Car car) { 181 | car.color = "Red"; 182 | } 183 | ``` 184 | **[⬆ về đầu trang](#mục-lục)** 185 | 186 | ## **Hàm** 187 | ### Đối số của hàm (lý tưởng là ít hơn hoặc bằng 2) 188 | Giới hạn số lượng param của hàm là một điều cực kì quan trọng bởi vì nó làm cho 189 | hàm của bạn trở nên dễ test hơn. Trường hợp có nhiều hơn 3 params có thể dẫn 190 | đến việc bạn phải test hàng tấn test case khác nhau với những đối số riêng biệt. 191 | 192 | 1 hoặc 2 đối số là trường hợp lý tưởng, còn trường hợp 3 đối số thì nên tránh 193 | nếu có thể. Những trường hợp khác (từ 3 params trở lên) thì nên được gộp lại. 194 | Thông thường nếu có nhiều hơn 2 đối số thì hàm của bạn đang cố thực hiện quá 195 | nhiều việc rồi đấy. Trong trường hợp ngược lại, phần lớn thời gian một đối 196 | tượng cấp cao sẽ là đủ để làm đối số. 197 | 198 | 199 | **[⬆ về đầu trang](#mục-lục)** 200 | 201 | 202 | ### Hàm chỉ nên giải quyết một vấn đề 203 | Đây là quy định quan trọng nhất của kỹ thuật phần mềm. Khi một hàm thực hiện 204 | nhiều hơn 1 việc, chúng sẽ trở nên khó khăn hơn để viết code, test, và suy luận. 205 | Khi bạn có thể tách biệt một hàm để chỉ thực hiện một hành động, thì sẽ dễ dàng 206 | hơn để tái cấu trúc và code của bạn sẽ dễ đọc hơn nhiều. Nếu bạn chỉ cần làm theo 207 | hướng dẫn này thôi mà không cần làm gì khác thì bạn cũng đã giỏi hơn nhiều 208 | developer khác rồi. 209 | 210 | **Không tốt:** 211 | ```java 212 | public void emailClients(List clients) { 213 | for (Client client : clients) { 214 | Client clientRecord = repository.findOne(client.getId()); 215 | if (clientRecord.isActive()){ 216 | email(client); 217 | } 218 | } 219 | } 220 | ``` 221 | 222 | **Tốt:** 223 | ```java 224 | public void emailClients(List clients) { 225 | for (Client client : clients) { 226 | if (isActiveClient(client)) { 227 | email(client); 228 | } 229 | } 230 | } 231 | 232 | private boolean isActiveClient(Client client) { 233 | Client clientRecord = repository.findOne(client.getId()); 234 | return clientRecord.isActive(); 235 | } 236 | ``` 237 | 238 | **[⬆ Về đầu trang](#mục-lục)** 239 | 240 | ### Tên hàm phải nói ra được những gì chúng làm 241 | 242 | **Không tốt:** 243 | ```java 244 | private void addToDate(Date date, int month){ 245 | //.. 246 | } 247 | 248 | Date date = new Date(); 249 | 250 | // Khó để biết được hàm này thêm gì thông qua tên hàm. 251 | addToDate(date, 1); 252 | ``` 253 | **Tốt:** 254 | ```java 255 | private void addMonthToDate(Date date, int month){ 256 | //.. 257 | } 258 | 259 | Date date = new Date(); 260 | addMonthToDate(1, date); 261 | ``` 262 | **[⬆ Về đầu trang](#mục-lục)** 263 | 264 | ### Xóa code trùng lặp 265 | Tuyệt đối tránh những dòng code trùng lặp. Code trùng lặp thì không tốt bởi vì 266 | nếu bạn cần thay đổi cùng một logic, bạn phải sửa ở nhiều hơn một nơi. 267 | 268 | Hãy tưởng tượng nếu bạn điều hành một nhà hàng và bạn theo dõi hàng tồn kho: 269 | bao gồm cà chua, hành tây, tỏi, gia vị, vv.... Nếu bạn có nhiều danh sách 270 | quản lý, thì tất cả chúng phải được thay đổi khi bạn phục vụ một món ăn có 271 | chứa cà chua. Nếu bạn chỉ có 1 danh sách, thì việc cập nhật ở một nơi thôi. 272 | 273 | Thông thường, bạn có những dòng code lặp lại bởi vì bạn có 2 hay nhiều hơn 274 | những thứ chỉ khác nhau chút ít, mà chia sẻ nhiều thứ chung, nhưng sự khác 275 | nhau của chúng buộc bạn phải có 2 hay nhiều hàm riêng biệt để làm nhiều điều 276 | tương tự nhau. Xóa đi những dòng code trùng có nghĩa là tạo ra một abstraction 277 | có thể xử lý tập những điểm khác biệt này chỉ với một hàm/module hay class. 278 | 279 | Có được một abstraction đúng thì rất quan trọng, đó là lý do tại sao bạn nên 280 | tuân thủ các nguyên tắc SOLID được đặt ra trong phần *Lớp*. Những abstraction 281 | không tốt có thể còn tệ hơn cả những dòng code bị trùng lặp, vì thế hãy cẩn 282 | thận! Nếu bạn có thể tạo ra một abstraction tốt, hãy làm nó! Đừng lặp lại chính 283 | mình, nếu bạn không muốn đi cập nhật nhiều nơi bất cứ khi nào bạn muốn thay đổi 284 | một thứ gì đó. 285 | 286 | **[⬆ Về đầu trang](#mục-lục)** 287 | 288 | ### Tránh những ảnh hưởng phụ (Side Effect) 289 | Một hàm tạo ra ảnh hưởng phụ nếu nó làm bất kì điều gì khác hơn là nhận một giá 290 | trị đầu vào và trả về một hoặc nhiều giá trị. Ảnh hưởng phụ có thể là ghi một 291 | file, thay đổi vài biến toàn cục, hoặc vô tình đưa tất cả tiền của bạn cho một 292 | người lạ. 293 | 294 | Bây giờ, cũng có khi bạn cần ảnh hưởng phụ trong một chương trình. Giống như ví dụ 295 | trước, bạn cần ghi một file. Những gì bạn cần làm là tập trung vào nơi bạn sẽ làm 296 | nó. Đừng viết hàm và lớp riêng biệt để tạo ra một file cụ thể. Hãy có một service 297 | để viết nó. Một và chỉ một. 298 | 299 | Điểm chính là để tránh những lỗi chung như chia sẻ trạng thái giữa những đối tượng 300 | mà không có bất kì cấu trúc nào, sử dụng các kiểu dữ liệu có thể thay đổi được mà 301 | có thể được ghi bởi bất cứ thứ gì, và không tập trung nơi có thể xảy ra các ảnh hưởng 302 | phụ. Nếu bạn có thể làm điều đó, bạn sẽ hạnh phúc hơn so với phần lớn các lập trình 303 | viên khác đấy. 304 | 305 | 306 | **Không tốt:** 307 | ```javascript 308 | // Biến toàn cục được tham chiếu bởi hàm dưới đây. 309 | // Nếu chúng ta có một hàm khác sử dụng name, nó sẽ trở thành một array 310 | let name = 'Ryan McDermott'; 311 | 312 | function splitIntoFirstAndLastName() { 313 | name = name.split(' '); 314 | } 315 | 316 | splitIntoFirstAndLastName(); 317 | 318 | console.log(name); // ['Ryan', 'McDermott']; 319 | ``` 320 | 321 | **Tốt:** 322 | ```javascript 323 | function splitIntoFirstAndLastName(name) { 324 | return name.split(' '); 325 | } 326 | 327 | const name = 'Ryan McDermott'; 328 | const newName = splitIntoFirstAndLastName(name); 329 | 330 | console.log(name); // 'Ryan McDermott'; 331 | console.log(newName); // ['Ryan', 'McDermott']; 332 | ``` 333 | **[⬆ back to top](#mục-lục)** 334 | 335 | ### Đóng gói các điều kiện 336 | 337 | **Không tốt:** 338 | ```java 339 | if ('fetching'.equal(fsm.getState()) && listNode.isEmpty()) { 340 | // ... 341 | } 342 | ``` 343 | 344 | **Tốt:** 345 | ```java 346 | boolean shouldShowProgressBar(fsm, listNode) { 347 | return 'fetching'.equal(fsm.getState()) && listNode.isEmpty(); 348 | } 349 | 350 | if (shouldShowProgressBar(fsmInstance, listNodeInstance)) { 351 | // ... 352 | } 353 | ``` 354 | 355 | **[⬆ back to top](#mục-lục)** 356 | 357 | ### Trách những điều kiện phủ định 358 | 359 | **Không tốt:** 360 | ```java 361 | boolean isDOMNodeNotPresent(node) { 362 | // ... 363 | } 364 | 365 | if (!isDOMNodeNotPresent(node)) { 366 | // ... 367 | } 368 | ``` 369 | 370 | **Tốt:** 371 | ```java 372 | boolean isDOMNodePresent(node) { 373 | // ... 374 | } 375 | 376 | if (isDOMNodePresent(node)) { 377 | // ... 378 | } 379 | ``` 380 | 381 | **[⬆ back to top](#mục-lục)** 382 | 383 | ### Tránh điều kiện 384 | Đây dường như là một việc bất khả thi. Khi nghe điều này đầu tiên, hầu hết mọi 385 | người đều nói, "Làm sao tôi cần phải làm gì mà không có mệnh đề `if`?" 386 | Câu trả lời là bạn có thể sử dụng tính đa hình để đạt được công việc tương tự 387 | trong rất nhiều trường hợp. Câu hỏi thứ hai thường là "Đó là điều tốt nhưng tại 388 | sao tôi lại muốn làm điều đó?" Câu trả lời là khái niệm mà ta đã học ở phần 389 | trước: một hàm chỉ nên thực hiện một việc. Khi bạn có nhiều lớp và hàm mà có 390 | nhiều mệnh đề `if`, bạn đang cho người dùng của bạn biết rằng hàm của bạn đang 391 | làm nhiều hơn một việc. Hãy nhớ, chỉ làm một công việc thôi. 392 | 393 | **Không tốt:** 394 | ```javascript 395 | class Airplane { 396 | // ... 397 | getCruisingAltitude() { 398 | switch (this.type) { 399 | case '777': 400 | return this.getMaxAltitude() - this.getPassengerCount(); 401 | case 'Air Force One': 402 | return this.getMaxAltitude(); 403 | case 'Cessna': 404 | return this.getMaxAltitude() - this.getFuelExpenditure(); 405 | } 406 | } 407 | } 408 | ``` 409 | 410 | **Tốt:** 411 | ```javascript 412 | class Airplane { 413 | // ... 414 | } 415 | 416 | class Boeing777 extends Airplane { 417 | // ... 418 | getCruisingAltitude() { 419 | return this.getMaxAltitude() - this.getPassengerCount(); 420 | } 421 | } 422 | 423 | class AirForceOne extends Airplane { 424 | // ... 425 | getCruisingAltitude() { 426 | return this.getMaxAltitude(); 427 | } 428 | } 429 | 430 | class Cessna extends Airplane { 431 | // ... 432 | getCruisingAltitude() { 433 | return this.getMaxAltitude() - this.getFuelExpenditure(); 434 | } 435 | } 436 | ``` 437 | **[⬆ back to top](#mục-lục)** 438 | 439 | ### Xóa code chết (dead code) 440 | Dead code cũng tệ như code trùng lặp. Không có lý do gì để giữ chúng lại trong 441 | codebase của bạn. Nếu nó không được gọi nữa, hãy bỏ nó đi! Nó vẫn sẽ nằm trong 442 | lịch sử phiên bản của bạn nếu bạn vẫn cần nó. 443 | 444 | **[⬆ back to top](#mục-lục)** 445 | 446 | ## **Đối tượng và Cấu trúc dữ liệu** 447 | ### Sử dụng getter và setter 448 | Đây là một danh sách các lí do tại sao: 449 | 450 | * Khi bạn muốn thực hiện nhiều hơn việc lấy một thuộc tính của đối tượng, bạn không 451 | cần phải tìm kiếm và thay đổi mỗi accessor trong codebase của bạn. 452 | * Làm cho việc thêm các validation đơn giản khi thực hiện trên một `tập hợp`. 453 | * Đóng gói các biểu diễn nội bộ. 454 | * Dễ dàng thêm log và xử lí lỗi khi getting và setting. 455 | * Kế thừa lớp này, bạn có thể override những hàm mặc định. 456 | * Bạn có thể lazy load các thuộc tính của một đối tượng, lấy nó từ server. 457 | 458 | **[⬆ back to top](#mục-lục)** 459 | 460 | ## **Lớp** 461 | ### Sử dụng hàm khởi tạo (constructor) quá nhiều biến hoặc gọi nhiều hàm set liên tiếp nhau 462 | - Tránh sử dụng hàm constructor với nhiều hơn 3 đối số truyền vào thay vì vậy hãy áp dụng Builder Pattern 463 | 464 | ### Ưu tiên thành phần hơn là kế thừa 465 | - Như đã được nhấn mạnh nhiều trong [*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) 466 | của Gang of Four, bạn nên sử dụng cấu trúc thành phần hơn là thừa kế nếu có thể. 467 | Có rất nhiều lý do tốt để sử dụng kế thừa cũng như sử dụng thành phần. 468 | Điểm nhấn cho phương châm này đó là nếu tâm trí của bạn đi theo bản năng thừa kế, 469 | thử nghĩ nếu thành phần có thể mô hình vấn đề của bạn tốt hơn. Trong một số trường 470 | hợp nó có thể. 471 | 472 | Bạn có thể tự hỏi, "khi nào tôi nên sử dụng thừa kế?" Nó phụ thuộc vào 473 | vấn đề trong tầm tay của bạn, nhưng đây là một danh sách manh nha khi kế thừa 474 | có ý nghĩa hơn thành phần: 475 | 476 | 1. Kế thừa của bạn đại diện cho mỗi quan hệ "is-a" và không có mỗi quan hệ "has-a" 477 | (Human->Animal vs. User->UserDetails). 478 | 2. Bạn có thể sử dụng lại code từ lớp cơ bản (Humans có thể di chuyển giống tất cả Animals). 479 | 3. Bạn muốn làm thay đổi toàn cục đến các lớp dẫn xuất bằng cách thay đổi lớp cơ bản. 480 | 481 | **[⬆ back to top](#mục-lục)** 482 | 483 | ## **SOLID** 484 | ### Nguyên lí đơn trách nhiệm (Single Responsibility Principle) 485 | Như đã được nói đến trong cuốn Clean Code, "Chỉ có thể thay đổi một lớp vì một lí 486 | do duy nhất". Thật là hấp dẫn để nhồi nhét nhiều chức năng vào cho một lớp, giống 487 | như là khi bạn chỉ có thể lấy một chiếc vali cho chuyến bay vậy. Vấn đề là lớp của 488 | bạn sẽ không được hiểu gắn kết về mặt khái niệm của nó và sẽ có rất nhiều lí do 489 | để thay đổi. Việc làm giảm thiểu số lần bạn cần phải thay đổi một lớp là một việc 490 | quan trọng. Nó quan trọng bởi vì nếu có quá nhiều chức năng trong một lớp và bạn 491 | chỉ muốn thay đổi một chút xíu của lớp đó, thì có thể sẽ rất khó để hiểu được việc 492 | thay đổi đó sẽ ảnh hưởng đến những module khác trong codebase như thế nào. 493 | 494 | **Không tốt:** 495 | ```java 496 | class UserSettings { 497 | private User user; 498 | UserSettings(User user) { 499 | this.user = user; 500 | } 501 | 502 | void changeSettings(UserSettings settings) { 503 | if (this.verifyCredentials()) { 504 | // ... 505 | } 506 | } 507 | 508 | boolean verifyCredentials() { 509 | // ... 510 | } 511 | } 512 | ``` 513 | 514 | **Tốt:** 515 | ```java 516 | class UserAuth { 517 | private User user; 518 | UserAuth(User user) { 519 | this.user = user; 520 | } 521 | 522 | boolean verifyCredentials() { 523 | // ... 524 | } 525 | } 526 | 527 | 528 | class UserSettings { 529 | private User user; 530 | UserSettings(User user) { 531 | this.user = user; 532 | this.auth = new UserAuth(user); 533 | } 534 | 535 | void changeSettings(UserSettings settings) { 536 | if (this.auth.verifyCredentials()) { 537 | // ... 538 | } 539 | } 540 | } 541 | ``` 542 | **[⬆ back to top](#mục-lục)** 543 | 544 | ### Nguyên lí đóng mở (Open/Closed Principle) 545 | Betrand Meyer đã nói "có thể thoải mái mở rộng một module, nhưng hạn chế sửa 546 | đổi bên trong module đó". Điều đó nghĩa là gì? Nguyên tắc này cơ bản nhấn mạnh 547 | rằng bạn phải cho phép người dùng thêm các chức năng mới mà không làm thay 548 | đổi các code đang có. 549 | 550 | **Không tốt:** 551 | ```javascript 552 | class AjaxAdapter extends Adapter { 553 | constructor() { 554 | super(); 555 | this.name = 'ajaxAdapter'; 556 | } 557 | } 558 | 559 | class NodeAdapter extends Adapter { 560 | constructor() { 561 | super(); 562 | this.name = 'nodeAdapter'; 563 | } 564 | } 565 | 566 | class HttpRequester { 567 | constructor(adapter) { 568 | this.adapter = adapter; 569 | } 570 | 571 | fetch(url) { 572 | if (this.adapter.name === 'ajaxAdapter') { 573 | return makeAjaxCall(url).then((response) => { 574 | // transform response and return 575 | }); 576 | } else if (this.adapter.name === 'httpNodeAdapter') { 577 | return makeHttpCall(url).then((response) => { 578 | // transform response and return 579 | }); 580 | } 581 | } 582 | } 583 | 584 | function makeAjaxCall(url) { 585 | // request and return promise 586 | } 587 | 588 | function makeHttpCall(url) { 589 | // request and return promise 590 | } 591 | ``` 592 | 593 | **Tốt:** 594 | ```javascript 595 | class AjaxAdapter extends Adapter { 596 | constructor() { 597 | super(); 598 | this.name = 'ajaxAdapter'; 599 | } 600 | 601 | request(url) { 602 | // request and return promise 603 | } 604 | } 605 | 606 | class NodeAdapter extends Adapter { 607 | constructor() { 608 | super(); 609 | this.name = 'nodeAdapter'; 610 | } 611 | 612 | request(url) { 613 | // request and return promise 614 | } 615 | } 616 | 617 | class HttpRequester { 618 | constructor(adapter) { 619 | this.adapter = adapter; 620 | } 621 | 622 | fetch(url) { 623 | return this.adapter.request(url).then((response) => { 624 | // transform response and return 625 | }); 626 | } 627 | } 628 | ``` 629 | 630 | **[⬆ back to top](#mục-lục)** 631 | 632 | ### Nguyên lí thay thế Liskov (Liskov Substitution Principle) 633 | Đây là một thuật ngữ đáng sợ cho một khái niệm rất đơn giản. Nó được định nghĩa 634 | một cách chính thức là: "Nếu S là một kiểu con của T, thì các đối tượng của kiểu 635 | T có thể được thay thế bằng các đối tượng của kiểu S (ví dụ các đối tượng của 636 | kiểu S có thể thay thế các đối tượng của kiểu T) mà không làm thay đổi bất kì thuộc 637 | tính mong muốn nào của chương trình đó (tính chính xác, thực hiện tác vụ, ..). 638 | Đó thậm chí còn là một định nghĩa đáng sợ hơn. 639 | 640 | Sự giải thích tốt nhất cho nguyên lí này là, nếu bạn có một lớp cha và một lớp con, 641 | thì lớp cơ sở và lớp con có thể được sử dụng thay thế cho nhau mà không làm thay 642 | đổi tính đúng đắn của chương trình. Có thể vẫn còn hơi rối ở đây, vậy hãy xem 643 | cái ví dụ cổ điển hình vuông-hình chữ nhật (Square-Rectangle) dưới đây. Về mặt 644 | toán học, một hình vuông là một hình chữ nhật, tuy nhiên nếu bạn mô hình hoá điều 645 | này sử dụng quan hệ "is a" thông qua việc kế thừa, bạn sẽ nhanh chóng gặp phải 646 | rắc rối đấy. 647 | 648 | **Không tốt:** 649 | ```javascript 650 | class Rectangle { 651 | constructor() { 652 | this.width = 0; 653 | this.height = 0; 654 | } 655 | 656 | setColor(color) { 657 | // ... 658 | } 659 | 660 | render(area) { 661 | // ... 662 | } 663 | 664 | setWidth(width) { 665 | this.width = width; 666 | } 667 | 668 | setHeight(height) { 669 | this.height = height; 670 | } 671 | 672 | getArea() { 673 | return this.width * this.height; 674 | } 675 | } 676 | 677 | class Square extends Rectangle { 678 | setWidth(width) { 679 | this.width = width; 680 | this.height = width; 681 | } 682 | 683 | setHeight(height) { 684 | this.width = height; 685 | this.height = height; 686 | } 687 | } 688 | 689 | function renderLargeRectangles(rectangles) { 690 | rectangles.forEach((rectangle) => { 691 | rectangle.setWidth(4); 692 | rectangle.setHeight(5); 693 | const area = rectangle.getArea(); // BAD: Will return 25 for Square. Should be 20. 694 | rectangle.render(area); 695 | }); 696 | } 697 | 698 | const rectangles = [new Rectangle(), new Rectangle(), new Square()]; 699 | renderLargeRectangles(rectangles); 700 | ``` 701 | 702 | **Tốt:** 703 | ```javascript 704 | class Shape { 705 | setColor(color) { 706 | // ... 707 | } 708 | 709 | render(area) { 710 | // ... 711 | } 712 | } 713 | 714 | class Rectangle extends Shape { 715 | constructor(width, height) { 716 | super(); 717 | this.width = width; 718 | this.height = height; 719 | } 720 | 721 | getArea() { 722 | return this.width * this.height; 723 | } 724 | } 725 | 726 | class Square extends Shape { 727 | constructor(length) { 728 | super(); 729 | this.length = length; 730 | } 731 | 732 | getArea() { 733 | return this.length * this.length; 734 | } 735 | } 736 | 737 | function renderLargeShapes(shapes) { 738 | shapes.forEach((shape) => { 739 | const area = shape.getArea(); 740 | shape.render(area); 741 | }); 742 | } 743 | 744 | const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)]; 745 | renderLargeShapes(shapes); 746 | ``` 747 | 748 | **[⬆ back to top](#mục-lục)** 749 | 750 | ### Nguyên lí phân tách interface (Interface Segregation Principle) 751 | Nguyên lí phân tách interface nhấn mạnh rằng "Người dùng không nên bị bắt 752 | buộc phải phụ thuộc vào các interfaces mà họ không sử dụng." 753 | 754 | Một ví dụ tốt để minh hoạ cho nguyên lí này trong Java là các lớp mà yêu 755 | cầu cài đặt các đối tượng lớn. Việc không yêu cầu người dùng thiết lập một số 756 | lượng lớn các phương thức là một điều tốt, bởi vì đa số họ không cần tất 757 | cả các cài đặt. Làm cho chúng trở thành tuỳ chọn giúp tránh được việc có một 758 | "fat interface". 759 | 760 | 761 | **[⬆ back to top](#mục-lục)** 762 | 763 | ### Nguyên lí đảo ngược dependency (Dependency Inversion Principle) 764 | Với cách code thông thường, các module cấp cao sẽ gọi các module cấp thấp. 765 | Module cấp cao sẽ phụ thuộc và module cấp thấp, điều đó tạo ra các dependency. 766 | Khi module cấp thấp thay đổi, module cấp cao phải thay đổi theo. 767 | Một thay đổi sẽ kéo theo hàng loạt thay đổi, giảm khả năng bảo trì của code. 768 | 769 | Nguyên lí này khẳng định hai điều cần thiết sau: 770 | 1. Nhưng module cấp cao không nên phụ thuộc vào những module cấp thấp. Cả 771 | hai nên phụ thuộc vào abstraction. 772 | 2. Abstraction (interface) không nên phụ thuộc vào chi tiết, mà ngược lại. 773 | 774 | Điều này có thể khó hiểu lúc ban đầu, nhưng nếu bạn đã từng làm việc với Angular.js, 775 | bạn đã thấy một sự hiện thực của nguyên lí này trong dạng của Dependency Injection 776 | (DI). Khi chúng không phải là các khái niệm giống nhau, DIP giữ cho module cấp 777 | cao không biết chi tiết các module cấp thấp của nó và thiết lập chúng. Có thể đạt 778 | được điều này thông qua DI. Một lợi ích to lớn của DIP là nó làm giảm sự phụ thuộc 779 | lẫn nhau giữa các module. Sự phụ thuộc lẫn nhau là một kiểu mẫu không tốt, vì nó 780 | làm cho việc tái cấu trúc code trở nên khó khăn. 781 | 782 | **Không tốt:** 783 | ```java 784 | // Cart là module cấp cao 785 | public class Cart 786 | { 787 | public void Checkout(int orderId, int userId) 788 | { 789 | // Database, Logger, EmailSender là module cấp thấp 790 | Database db = new Database(); 791 | db.Save(orderId); 792 | 793 | Logger log = new Logger(); 794 | log.LogInfo("Order has been checkout"); 795 | 796 | EmailSender es = new EmailSender(); 797 | es.SendEmail(userId); 798 | } 799 | } 800 | ``` 801 | 802 | **Tốt:** 803 | ```java 804 | // Interface 805 | public interface IDatabase 806 | { 807 | void Save(int orderId); 808 | } 809 | public interface ILogger 810 | { 811 | void LogInfo(string info); 812 | } 813 | public interface IEmailSender 814 | { 815 | void SendEmail(int userId); 816 | } 817 | 818 | // Các Module implement các Interface 819 | public class Logger : ILogger 820 | { 821 | public void LogInfo(string info) {} 822 | } 823 | public class Database : IDatabase 824 | { 825 | public void Save(int orderId) {} 826 | } 827 | public class EmailSender : IEmailSender 828 | { 829 | public void SendEmail(int userId) {} 830 | } 831 | 832 | // Hàm checkout mới sẽ như sau 833 | public void Checkout(int orderId, int userId) 834 | { 835 | // Nếu muốn thay đổi database, logger ta chỉ cần thay dòng code dưới 836 | // Các Module XMLDatabase, SQLDatabase phải implement IDatabase 837 | //IDatabase db = new XMLDatabase(); 838 | //IDatebase db = new SQLDatabase(); 839 | IDatabase db = new Database(); 840 | db.Save(orderId); 841 | 842 | ILogger log = new Logger(); 843 | log.LogInfo("Order has been checkout"); 844 | 845 | IEmailSender es = new EmailSender(); 846 | es.SendEmail(userId); 847 | } 848 | ``` 849 | Trong thực tế, người ta thường áp dụng pattern DI (Dependency Injection) để đảm bảo nguyên lý DIP (Dependency Inversion Principle) trong code. 850 | **[⬆ back to top](#mục-lục)** 851 | 852 | ## **Testing** 853 | Testing thì quan trọng hơn shipping. Nếu bạn không có test hoặc không đủ, 854 | thì mỗi lần ship code bạn sẽ không chắc là mình có làm hư hại thứ gì không. 855 | Việc quyết định những gì để tạo thành số lượng test đủ là do team của bạn, 856 | nhưng việc có 100% độ bao phủ (tất cả các câu lệnh và rẽ nhánh) là cách để 857 | bạn đạt được sự tự tin cao. Điều này có nghĩa ngoài việc có được một framework 858 | để test tốt, bạn cũng cần sử dụng một công cụ tính độ bao phủ tốt 859 | 860 | Không có lí do gì để không viết test. Có rất nhiều framework test Java tốt, 861 | vì thế hãy tìm một framework mà team bạn thích. Khi đã tìm được một cái thích 862 | hợp với team của mình, hãy đặt mục tiêu để luôn luôn viết test cho mỗi tính 863 | năng hoặc module mới của bạn. Nếu phương pháp test ưa thích của bạn là Test 864 | Driven Development (TDD), điều đó thật tuyệt, nhưng điểm quan trọng là phải 865 | chắc chắn bạn đạt được mục tiêu về độ bao phủ trước khi launch một tính năng 866 | hoặc refactor một tính năng cũ nào đó. 867 | 868 | **[⬆ back to top](#mục-lục)** 869 | 870 | ## **Xử lí đồng thời** 871 | ### Hãy dùng higher-order function, đừng dùng callback tránh callback hell 872 | Tham khảo thư viện [ReactiveX/RxJava](https://github.com/ReactiveX/RxJava) 873 | 874 | **[⬆ back to top](#mục-lục)** 875 | 876 | ## **Xử lí lỗi** 877 | Thông báo lỗi là một điều tốt! Nghĩa là chương trình của bạn nhận dạng 878 | được khi có một cái gì đó chạy không đúng và nó sẽ cho bạn biết bằng việc 879 | dừng chức năng mà nó đang thực thi và thông 880 | báo cho bạn trong console với một log để theo dấu. 881 | 882 | ### Đừng bỏ qua những lỗi đã bắt được 883 | Nếu không làm gì với lỗi đã bắt được, bạn sẽ không thể sửa hoặc phản ứng 884 | lại được với lỗi đó. Ghi lỗi ra console (`console.log`) cũng không tốt hơn 885 | bao nhiêu vì đa số nó có thể bị trôi mất trong một đống những thứ được hiển 886 | thị ra ở console. Nếu bạn đặt bất cứ đoạn code nào trong một block `try/catch`, 887 | tức là bạn nghĩ một lỗi có thể xảy ra ở đây, do đó bạn nên có một giải pháp 888 | hoặc tạo một luồng code để xử lí lỗi khi nó xảy ra. 889 | 890 | **Không tốt:** 891 | ```javascript 892 | try { 893 | functionThatMightThrow(); 894 | } catch (error) { 895 | console.log(error); 896 | } 897 | ``` 898 | 899 | **Tốt:** 900 | ```javascript 901 | try { 902 | functionThatMightThrow(); 903 | } catch (error) { 904 | // One option (more noisy than console.log): 905 | console.error(error); 906 | // Another option: 907 | notifyUserOfError(error); 908 | // Another option: 909 | reportErrorToService(error); 910 | // OR do all three! 911 | } 912 | ``` 913 | 914 | **[⬆ back to top](#mục-lục)** 915 | 916 | ## **Định dạng** 917 | Việc định dạng code mang tính chủ quan. Giống như nhiều quy tắc được trình 918 | bày trong tài liệu này, không có quy tắc nào cứng nhắc và nhanh chóng mà bạn 919 | bắt buộc phải tuân theo. Điểm chính của phần này là ĐỪNG BAO GIỜ TRANH CÃI 920 | về việc định dạng code như thế nào. Thật tốn thời gian và 921 | tiền bạc chỉ để tranh cãi về vấn đề định dạng code. 922 | 923 | Đối với những thứ không thuộc phạm vi của việc tự động định dạng code (thụt đầu 924 | dòng, tab và space, nháy đơn và nháy kép,..) hãy xem một số hướng dẫn ở [đây](https://github.com/LobeSoftware/coding-standards/blob/master/Android.md). 925 | 926 | ### Các hàm gọi và hàm được gọi nên nằm gần nhau 927 | Nếu một hàm gọi một hàm khác, hãy giữ những hàm này nằm gần theo chiều dọc trong 928 | file. Lí tưởng là, hãy giữ cho hàm gọi ở trên hàm được gọi. Chúng ta có xu hướng 929 | đọc code từ trên xuống, giống như đọc báo vậy. Do đó, hãy làm cho code của chúng 930 | ta cũng được đọc theo cách đó. 931 | 932 | **[⬆ back to top](#mục-lục)** 933 | 934 | ## **Viết chú thích** 935 | ### Chỉ nên viết chú thích cho những thứ có logic phức tạp. 936 | Các chú thích thường là lời xin lỗi, chứ không phải là yêu cầu. 937 | Những đoạn code tốt thì *đa số* tự nó đã là tài liệu rồi. 938 | 939 | **Không tốt:** 940 | ```javascript 941 | function hashIt(data) { 942 | // Khai báo hash 943 | let hash = 0; 944 | 945 | // Lấy chiều dài của chuỗi 946 | const length = data.length; 947 | 948 | // Lặp qua mỗi kí tự 949 | for (let i = 0; i < length; i++) { 950 | // Lấy mã của kí tự 951 | const char = data.charCodeAt(i); 952 | // Gán giá trị cho hash 953 | hash = ((hash << 5) - hash) + char; 954 | // Chuyển thành định dạng số nguyên 32 bit 955 | hash &= hash; 956 | } 957 | } 958 | ``` 959 | 960 | **Tốt:** 961 | ```javascript 962 | 963 | function hashIt(data) { 964 | let hash = 0; 965 | const length = data.length; 966 | 967 | for (let i = 0; i < length; i++) { 968 | const char = data.charCodeAt(i); 969 | hash = ((hash << 5) - hash) + char; 970 | 971 | // Chuyển thành định dạng số nguyên 32 bit 972 | hash &= hash; 973 | } 974 | } 975 | 976 | ``` 977 | **[⬆ back to top](#mục-lục)** 978 | 979 | ### Đừng giữ lại những đoạn code bị chú thích 980 | Những công cụ quản lí phiên bản sinh ra để làm nhiệm vụ của chúng. 981 | Hãy để code cũ của bạn nằm lại trong dĩ vãng đi. 982 | 983 | **Không tốt:** 984 | ```javascript 985 | doStuff(); 986 | // doOtherStuff(); 987 | // doSomeMoreStuff(); 988 | // doSoMuchStuff(); 989 | ``` 990 | 991 | **Tốt:** 992 | ```javascript 993 | doStuff(); 994 | ``` 995 | **[⬆ back to top](#mục-lục)** 996 | 997 | ### Đừng viết các chú thích nhật ký. 998 | Hãy nhớ, sử dụng công cụ quản lí phiên bản như Git! Chúng ta không cần những đoạn code 999 | vô dụng, bị chú thích và đặc biệt là những chú thích dạng nhật ký... 1000 | Sử dụng `git log` để xem lịch sử được mà! 1001 | 1002 | **Không tốt:** 1003 | ```javascript 1004 | /** 1005 | * 2016-12-20: Removed monads, didn't understand them (RM) 1006 | * 2016-10-01: Improved using special monads (JP) 1007 | * 2016-02-03: Removed type-checking (LI) 1008 | * 2015-03-14: Added combine with type-checking (JR) 1009 | */ 1010 | function combine(a, b) { 1011 | return a + b; 1012 | } 1013 | ``` 1014 | 1015 | **Tốt:** 1016 | ```javascript 1017 | function combine(a, b) { 1018 | return a + b; 1019 | } 1020 | ``` 1021 | **[⬆ back to top](#mục-lục)** 1022 | 1023 | ### Tránh những đánh dấu vị trí 1024 | Chúng thường xuyên làm nhiễu code. Hãy để những tên hàm, biến cùng với các 1025 | định dạng thích hợp tự tạo thành cấu trúc trực quan cho code của bạn. 1026 | 1027 | **Không tốt:** 1028 | ```javascript 1029 | //////////////////////////////////////////////////////////////////////////////// 1030 | // Scope Model Instantiation 1031 | //////////////////////////////////////////////////////////////////////////////// 1032 | $scope.model = { 1033 | menu: 'foo', 1034 | nav: 'bar' 1035 | }; 1036 | 1037 | //////////////////////////////////////////////////////////////////////////////// 1038 | // Action setup 1039 | //////////////////////////////////////////////////////////////////////////////// 1040 | const actions = function() { 1041 | // ... 1042 | }; 1043 | ``` 1044 | 1045 | **Tốt:** 1046 | ```javascript 1047 | $scope.model = { 1048 | menu: 'foo', 1049 | nav: 'bar' 1050 | }; 1051 | 1052 | const actions = function() { 1053 | // ... 1054 | }; 1055 | ``` 1056 | **[⬆ back to top](#mục-lục)** 1057 | --------------------------------------------------------------------------------