JavaRush /Blog Java /Random-VI /Quy tắc viết code: từ tạo hệ thống đến làm việc với đối t...

Quy tắc viết code: từ tạo hệ thống đến làm việc với đối tượng

Xuất bản trong nhóm
Chào buổi chiều mọi người: hôm nay tôi muốn nói chuyện với các bạn về việc viết code đúng. Khi tôi mới bắt đầu lập trình, không có ghi rõ ràng ở đâu là bạn có thể viết như thế này, và nếu bạn viết như thế này, tôi sẽ tìm thấy bạn và…. Kết quả là trong đầu tôi có rất nhiều câu hỏi: viết thế nào cho đúng, những nguyên tắc nào cần tuân thủ trong phần này hay phần kia của chương trình, v.v. Quy tắc viết code: từ tạo hệ thống đến làm việc với đối tượng - 1Chà, không phải ai cũng muốn ngay lập tức đi sâu vào những cuốn sách như Clean Code, vì trong đó có rất nhiều điều được viết, nhưng lúc đầu thì có rất ít điều rõ ràng. Và khi bạn đọc xong, bạn có thể ngăn cản mọi ham muốn viết mã. Vì vậy dựa trên những điều trên, hôm nay tôi muốn cung cấp cho các bạn một hướng dẫn nhỏ (một tập hợp các đề xuất nhỏ) để viết mã cấp cao hơn. Trong bài viết này, chúng ta sẽ tìm hiểu các quy tắc và khái niệm cơ bản liên quan đến việc tạo một hệ thống và làm việc với các giao diện, lớp và đối tượng. Đọc tài liệu này sẽ không làm bạn mất nhiều thời gian và tôi hy vọng nó sẽ không khiến bạn cảm thấy nhàm chán. Tôi sẽ đi từ trên xuống dưới, tức là từ cấu trúc chung của ứng dụng đến các chi tiết cụ thể hơn. Quy tắc viết code: từ tạo hệ thống đến làm việc với đối tượng - 2

Hệ thống

Các đặc tính mong muốn chung của hệ thống là:
  • độ phức tạp tối thiểu - nên tránh các dự án quá phức tạp. Điều chính là sự đơn giản và rõ ràng (tốt nhất = đơn giản);
  • dễ bảo trì - khi tạo một ứng dụng, bạn phải nhớ rằng nó sẽ cần được hỗ trợ (ngay cả khi đó không phải là bạn), vì vậy mã phải rõ ràng và dễ hiểu;
  • khớp nối yếu là số lượng kết nối tối thiểu giữa các phần khác nhau của chương trình (sử dụng tối đa nguyên tắc OOP);
  • khả năng sử dụng lại - thiết kế một hệ thống có khả năng tái sử dụng các mảnh của nó trong các ứng dụng khác;
  • tính di động - hệ thống phải dễ dàng thích nghi với môi trường khác;
  • phong cách duy nhất - thiết kế một hệ thống theo một phong cách duy nhất trong các phần khác nhau của nó;
  • khả năng mở rộng (khả năng mở rộng) - cải thiện hệ thống mà không làm ảnh hưởng đến cấu trúc cơ bản của nó (nếu bạn thêm hoặc thay đổi một đoạn, điều này sẽ không ảnh hưởng đến phần còn lại).
Hầu như không thể xây dựng một ứng dụng mà không cần sửa đổi mà không cần thêm chức năng. Chúng ta sẽ liên tục cần đưa ra những yếu tố mới để đứa con tinh thần của mình có thể theo kịp thời đại. Và đây là lúc khả năng mở rộng phát huy tác dụng . Khả năng mở rộng về cơ bản là mở rộng ứng dụng, thêm chức năng mới, làm việc với nhiều tài nguyên hơn (hay nói cách khác là tải nhiều hơn). Nghĩa là, chúng ta phải tuân thủ một số quy tắc, chẳng hạn như giảm tính ghép nối của hệ thống bằng cách tăng tính mô đun hóa, để việc thêm logic mới dễ dàng hơn.

Các giai đoạn thiết kế hệ thống

  1. Hệ thống phần mềm - thiết kế một ứng dụng ở dạng tổng quát.
  2. Tách thành các hệ thống/gói con - xác định các phần có thể tách rời một cách hợp lý và xác định các quy tắc tương tác giữa chúng.
  3. Chia hệ thống con thành các lớp - chia các phần của hệ thống thành các lớp và giao diện cụ thể, cũng như xác định sự tương tác giữa chúng.
  4. Việc chia lớp thành các phương thức là việc định nghĩa đầy đủ các phương thức cần thiết cho một lớp, dựa trên nhiệm vụ của lớp này. Thiết kế phương pháp - định nghĩa chi tiết về chức năng của từng phương pháp.
Thông thường, các nhà phát triển thông thường chịu trách nhiệm thiết kế và kiến ​​trúc sư ứng dụng chịu trách nhiệm về các hạng mục được mô tả ở trên.

Các nguyên tắc và khái niệm chính của thiết kế hệ thống

Thành ngữ khởi tạo lười biếng Một ứng dụng không dành thời gian tạo một đối tượng cho đến khi nó được sử dụng, điều này giúp tăng tốc quá trình khởi tạo và giảm tải trình thu gom rác. Nhưng bạn không nên đi quá xa với điều này vì điều này có thể dẫn đến vi phạm tính mô đun. Có thể đáng để chuyển tất cả các bước thiết kế sang một phần cụ thể, chẳng hạn như phần chính hoặc sang một lớp hoạt động giống như một nhà máy . Một trong những khía cạnh của mã tốt là không có mã soạn sẵn, lặp lại thường xuyên. Theo quy định, mã đó được đặt trong một lớp riêng biệt để có thể gọi nó vào đúng thời điểm. AOP Riêng biệt, tôi muốn đề cập đến lập trình hướng khía cạnh . Đây là lập trình bằng cách đưa ra logic end-to-end, tức là mã lặp lại được đưa vào các lớp - khía cạnh và được gọi khi đạt được một số điều kiện nhất định. Ví dụ: khi truy cập một phương thức có tên nhất định hoặc truy cập một biến thuộc một loại nhất định. Đôi khi các khía cạnh có thể gây nhầm lẫn vì không rõ mã được gọi từ đâu, tuy nhiên, đây là một chức năng rất hữu ích. Đặc biệt, khi lưu vào bộ nhớ đệm hoặc ghi nhật ký: chúng tôi thêm chức năng này mà không cần thêm logic bổ sung vào các lớp thông thường. Bạn có thể đọc thêm về OAP tại đây . 4 Quy Tắc Thiết Kế Kiến Trúc Đơn Giản Theo Kent Beck
  1. Tính biểu cảm - nhu cầu về mục đích được thể hiện rõ ràng của lớp, đạt được thông qua việc đặt tên chính xác, kích thước nhỏ và tuân thủ nguyên tắc trách nhiệm duy nhất (chúng ta sẽ xem xét chi tiết hơn bên dưới).
  2. Tối thiểu các lớp và phương thức - với mong muốn chia các lớp thành càng nhỏ và một chiều càng tốt, bạn có thể đi quá xa (phản mẫu - bắn súng). Nguyên tắc này đòi hỏi phải giữ cho hệ thống nhỏ gọn và không đi quá xa, tạo ra sự đẳng cấp cho mỗi lần hắt hơi.
  3. Thiếu trùng lặp - mã thừa gây nhầm lẫn là dấu hiệu của thiết kế hệ thống kém và được chuyển đến một nơi riêng biệt.
  4. Thực hiện tất cả các thử nghiệm - một hệ thống đã vượt qua tất cả các thử nghiệm sẽ được kiểm soát, vì bất kỳ thay đổi nào cũng có thể dẫn đến lỗi thử nghiệm, điều này có thể cho chúng ta thấy rằng một thay đổi trong logic bên trong của phương pháp cũng dẫn đến thay đổi trong hành vi dự kiến .
SOLID Khi thiết kế một hệ thống, cần tính đến các nguyên tắc nổi tiếng của SOLID: S - trách nhiệm duy nhất - nguyên tắc trách nhiệm duy nhất; O - mở đóng - nguyên tắc đóng/mở; L - Sự thay thế Liskov - Nguyên tắc thay thế của Barbara Liskov; I - phân tách giao diện - nguyên tắc phân tách giao diện; D - đảo ngược phụ thuộc - nguyên tắc đảo ngược phụ thuộc; Chúng tôi sẽ không đi sâu vào từng nguyên tắc cụ thể (điều này nằm ngoài phạm vi của bài viết này một chút, nhưng bạn có thể tìm hiểu thêm tại đây

Giao diện

Có lẽ một trong những giai đoạn quan trọng nhất của việc tạo một lớp phù hợp là tạo ra một giao diện phù hợp sẽ thể hiện sự trừu tượng tốt giúp che giấu các chi tiết triển khai của lớp, đồng thời sẽ thể hiện một nhóm các phương thức nhất quán rõ ràng với nhau . Chúng ta hãy xem xét kỹ hơn một trong các nguyên tắc SOLID - phân tách giao diện : khách hàng (các lớp) không nên triển khai các phương thức không cần thiết mà chúng sẽ không sử dụng. Nghĩa là, nếu chúng ta đang nói về việc xây dựng giao diện với số lượng phương thức tối thiểu nhằm thực hiện nhiệm vụ duy nhất của giao diện này (đối với tôi, nó rất giống với trách nhiệm duy nhất ), tốt hơn là tạo một vài phương thức nhỏ hơn thay vì một giao diện cồng kềnh. May mắn thay, một lớp có thể triển khai nhiều hơn một giao diện, như trường hợp kế thừa. Bạn cũng cần nhớ cách đặt tên chính xác cho các giao diện: tên phải phản ánh nhiệm vụ của nó một cách chính xác nhất có thể. Và tất nhiên, nó càng ngắn thì càng ít gây nhầm lẫn. Ở cấp độ giao diện, các nhận xét cho tài liệu thường được viết , từ đó giúp chúng ta mô tả chi tiết những gì phương thức nên làm, những đối số mà nó lấy và những gì nó sẽ trả về.

Lớp học

Quy tắc viết code: từ tạo hệ thống đến làm việc với đối tượng - 3Chúng ta hãy nhìn vào tổ chức nội bộ của các lớp học. Hay đúng hơn là một số quan điểm và quy tắc cần được tuân theo khi xây dựng các lớp. Thông thường, một lớp nên bắt đầu bằng danh sách các biến, được sắp xếp theo một thứ tự cụ thể:
  1. hằng số tĩnh công khai;
  2. hằng số tĩnh riêng;
  3. các biến thể hiện riêng tư.
Tiếp theo là các hàm tạo khác nhau theo thứ tự từ ít đến nhiều đối số. Theo sau chúng là các phương thức từ quyền truy cập mở hơn đến những phương thức đóng nhiều nhất: theo quy tắc, các phương thức riêng tư ẩn việc triển khai một số chức năng mà chúng tôi muốn hạn chế nằm ở dưới cùng.

Quy mô lớp học

Bây giờ tôi muốn nói về quy mô lớp học. Quy tắc viết code: từ tạo hệ thống đến làm việc với đối tượng - 4Chúng ta hãy nhớ một trong những nguyên tắc RẮN - trách nhiệm duy nhất . Trách nhiệm duy nhất - nguyên tắc trách nhiệm duy nhất. Nó nói rằng mỗi đối tượng chỉ có một mục tiêu (trách nhiệm) và logic của tất cả các phương thức của nó đều nhằm mục đích đảm bảo mục tiêu đó. Nghĩa là, dựa trên điều này, chúng ta nên tránh các lớp lớn, cồng kềnh (mà về bản chất chúng là một phản mẫu - “đối tượng thần thánh”), và nếu chúng ta có nhiều phương thức logic đa dạng, không đồng nhất trong một lớp, chúng ta cần phải suy nghĩ về việc chia nó thành một vài phần logic (lớp). Ngược lại, điều này sẽ cải thiện khả năng đọc mã, vì chúng ta không cần nhiều thời gian để hiểu mục đích của một phương thức nếu chúng ta biết mục đích gần đúng của một lớp nhất định. Bạn cũng cần để ý đến tên lớp : nó phải phản ánh logic mà nó chứa đựng. Giả sử, nếu chúng ta có một lớp có tên hơn 20 từ, chúng ta cần nghĩ đến việc tái cấu trúc. Mọi tầng lớp tự trọng không nên có số lượng biến nội bộ lớn như vậy. Trên thực tế, mỗi phương thức hoạt động với một trong số chúng hoặc một số phương thức, điều này gây ra sự liên kết lớn hơn trong lớp (đó chính xác là những gì nó phải như vậy, vì lớp phải là một tổng thể duy nhất). Kết quả là, việc tăng tính gắn kết của một lớp sẽ dẫn đến việc giảm tính gắn kết của nó, và tất nhiên, số lượng lớp của chúng ta sẽ tăng lên. Đối với một số người, điều này thật khó chịu; họ cần đến lớp nhiều hơn để xem một nhiệm vụ lớn cụ thể diễn ra như thế nào. Trong số những thứ khác, mỗi lớp là một mô-đun nhỏ cần được kết nối ở mức tối thiểu với các lớp khác. Sự cô lập này làm giảm số lượng thay đổi mà chúng ta cần thực hiện khi thêm logic bổ sung vào một lớp.

Các đối tượng

Quy tắc viết code: từ tạo hệ thống đến làm việc với đối tượng - 5

Đóng gói

Ở đây trước hết chúng ta sẽ nói về một trong những nguyên tắc của OOP - đóng gói . Vì vậy, việc ẩn quá trình triển khai không có nghĩa là tạo một lớp phương thức giữa các biến (hạn chế quyền truy cập thông qua các phương thức đơn lẻ, getters và setters, điều này không tốt vì toàn bộ điểm đóng gói bị mất). Ẩn quyền truy cập nhằm mục đích hình thành sự trừu tượng, nghĩa là lớp cung cấp các phương thức cụ thể phổ biến mà qua đó chúng ta làm việc với dữ liệu của mình. Nhưng người dùng không cần biết chính xác cách chúng tôi làm việc với dữ liệu này - nó hoạt động và điều đó không sao cả.

Luật Demeter

Bạn cũng có thể xem xét Luật Demeter: đây là một bộ quy tắc nhỏ giúp quản lý độ phức tạp ở cấp độ lớp và phương thức. Vì vậy, hãy giả sử rằng chúng ta có một đối tượng Carvà nó có một phương thức - move(Object arg1, Object arg2). Theo Law of Demeter, phương pháp này bị giới hạn trong việc gọi:
  • các phương thức của chính đối tượng đó Car(nói cách khác là this);
  • phương thức của các đối tượng được tạo trong move;
  • phương thức của các đối tượng được truyền làm đối số - arg1, arg2;
  • các phương thức của đối tượng bên trong Car(giống cái này).
Nói cách khác, luật Demeter giống như quy tắc của trẻ em - bạn có thể nói chuyện với bạn bè, nhưng không được nói chuyện với người lạ .

Cấu trúc dữ liệu

Cấu trúc dữ liệu là tập hợp các phần tử có liên quan với nhau. Khi coi một đối tượng là một cấu trúc dữ liệu, nó là một tập hợp các phần tử dữ liệu được xử lý bằng các phương thức, sự tồn tại của nó được ngầm hiểu. Nghĩa là, nó là một đối tượng có mục đích lưu trữ và vận hành (xử lý) dữ liệu được lưu trữ. Sự khác biệt chính so với một đối tượng thông thường là đối tượng là một tập hợp các phương thức hoạt động trên các phần tử dữ liệu mà sự tồn tại của chúng được ngụ ý. Bạn hiểu không? Trong một đối tượng thông thường, khía cạnh chính là các phương thức và các biến nội bộ nhằm mục đích hoạt động chính xác của chúng, nhưng trong cấu trúc dữ liệu thì ngược lại: các phương thức hỗ trợ và trợ giúp làm việc với các phần tử được lưu trữ, đó là điều chính ở đây. Một loại cấu trúc dữ liệu là Đối tượng truyền dữ liệu (DTO) . Đây là lớp có các biến công khai và không có phương thức (hoặc chỉ có phương thức đọc/ghi) truyền dữ liệu khi làm việc với cơ sở dữ liệu, làm việc với các thông báo phân tích cú pháp từ socket, v.v. Thông thường, dữ liệu trong các đối tượng như vậy không được lưu trữ trong một thời gian dài và được chuyển đổi gần như ngay lập tức thành thực thể mà ứng dụng của chúng tôi hoạt động. Ngược lại, một thực thể cũng là một cấu trúc dữ liệu, nhưng mục đích của nó là tham gia vào logic nghiệp vụ ở các cấp độ khác nhau của ứng dụng, trong khi DTO là vận chuyển dữ liệu đến/từ ứng dụng. Ví dụ DTO:
@Setter
@Getter
@NoArgsConstructor
public class UserDto {
    private long id;
    private String firstName;
    private String lastName;
    private String email;
    private String password;
}
Mọi thứ có vẻ rõ ràng, nhưng ở đây chúng ta tìm hiểu về sự tồn tại của giống lai. Kết hợp là các đối tượng chứa các phương thức để xử lý logic quan trọng và lưu trữ các phần tử bên trong cũng như các phương thức truy cập (get/set) đối với chúng. Những đối tượng như vậy rất lộn xộn và gây khó khăn cho việc thêm các phương thức mới. Bạn không nên sử dụng chúng vì không rõ chúng nhằm mục đích gì - lưu trữ các phần tử hoặc thực hiện một số loại logic. Bạn có thể đọc về các loại đối tượng có thể có ở đây .

Nguyên tắc tạo biến

Quy tắc viết code: từ tạo hệ thống đến làm việc với đối tượng - 6Chúng ta hãy suy nghĩ một chút về các biến, hay nói đúng hơn là nghĩ xem nguyên tắc tạo ra chúng có thể là gì:
  1. Tốt nhất, bạn nên khai báo và khởi tạo một biến ngay trước khi sử dụng (thay vì tạo rồi quên mất).
  2. Bất cứ khi nào có thể, hãy khai báo các biến là cuối cùng để ngăn giá trị của chúng thay đổi sau khi khởi tạo.
  3. Đừng quên các biến đếm (thông thường chúng ta sử dụng chúng trong một số loại vòng lặp for, nghĩa là chúng ta không được quên đặt lại chúng, nếu không nó có thể phá vỡ toàn bộ logic của chúng ta).
  4. Bạn nên thử khởi tạo các biến trong hàm tạo.
  5. Nếu có sự lựa chọn giữa việc sử dụng một đối tượng có hoặc không có tham chiếu ( new SomeObject()), hãy chọn không có ( ), vì đối tượng này sau khi được sử dụng sẽ bị xóa trong lần thu gom rác tiếp theo và sẽ không lãng phí tài nguyên.
  6. Làm cho thời gian tồn tại của biến càng ngắn càng tốt (khoảng cách giữa việc tạo biến và lần truy cập cuối cùng).
  7. Khởi tạo các biến được sử dụng trong vòng lặp ngay trước vòng lặp, thay vì ở đầu phương thức chứa vòng lặp.
  8. Luôn bắt đầu với phạm vi hạn chế nhất và chỉ mở rộng nó nếu cần thiết (bạn nên cố gắng biến biến càng cục bộ càng tốt).
  9. Sử dụng mỗi biến chỉ cho một mục đích.
  10. Tránh các biến có ý nghĩa ẩn (biến bị phân chia giữa hai nhiệm vụ, có nghĩa là loại của nó không phù hợp để giải quyết một trong số chúng).
Quy tắc viết code: từ tạo hệ thống đến làm việc với đối tượng - 7

phương pháp

Quy tắc viết code: từ tạo hệ thống đến làm việc với đối tượng - 8Hãy chuyển trực tiếp đến việc triển khai logic của chúng ta, cụ thể là các phương thức.
  1. Nguyên tắc đầu tiên là sự nhỏ gọn. Lý tưởng nhất là một phương thức không được vượt quá 20 dòng, vì vậy, nếu một phương thức công khai “phồng lên” đáng kể, bạn cần nghĩ đến việc chuyển logic riêng biệt sang các phương thức riêng tư.

  2. Quy tắc thứ hai là các khối trong lệnh if, else, whilev.v. không được lồng nhau nhiều: điều này làm giảm đáng kể khả năng đọc mã. Lý tưởng nhất là lồng không quá hai khối {}.

    Cũng nên làm cho mã trong các khối này nhỏ gọn và đơn giản.

  3. Quy tắc thứ ba là một phương thức chỉ được thực hiện một thao tác. Nghĩa là, nếu một phương thức thực hiện logic phức tạp, đa dạng, chúng ta sẽ chia nó thành các phương thức con. Kết quả là, bản thân phương thức này sẽ là một mặt tiền, mục đích của nó là gọi tất cả các hoạt động khác theo đúng thứ tự.

    Nhưng điều gì sẽ xảy ra nếu thao tác có vẻ quá đơn giản để tạo ra một phương thức riêng biệt? Đúng, đôi khi việc này có vẻ giống như bắn chim sẻ ra khỏi súng đại bác, nhưng những phương pháp nhỏ mang lại một số lợi ích:

    • đọc mã dễ dàng hơn;
    • các phương thức có xu hướng trở nên phức tạp hơn trong quá trình phát triển và nếu phương thức đó ban đầu đơn giản thì việc phức tạp hóa chức năng của nó sẽ dễ dàng hơn một chút;
    • ẩn chi tiết thực hiện;
    • tạo điều kiện tái sử dụng mã;
    • độ tin cậy mã cao hơn.
  4. Nguyên tắc hướng xuống là mã phải được đọc từ trên xuống dưới: logic càng thấp, độ sâu càng lớn và ngược lại, các phương thức càng cao, càng trừu tượng. Ví dụ: các lệnh chuyển đổi khá không gọn gàng và không mong muốn, nhưng nếu bạn không thể thực hiện mà không sử dụng nút chuyển, bạn nên cố gắng chuyển nó xuống mức thấp nhất có thể, sang các phương thức cấp thấp nhất.

  5. Đối số phương thức - có bao nhiêu đối số lý tưởng? Lý tưởng nhất là không có gì cả)) Nhưng điều đó có thực sự xảy ra không? Tuy nhiên, bạn nên cố gắng có càng ít chúng càng tốt, vì càng ít thì càng dễ sử dụng phương pháp này và càng dễ kiểm tra nó. Nếu nghi ngờ, hãy thử đoán tất cả các tình huống sử dụng một phương thức có số lượng lớn đối số đầu vào.

  6. Riêng biệt, tôi muốn nêu bật các phương thức có cờ boolean làm đối số đầu vào , vì điều này ngụ ý một cách tự nhiên rằng phương thức này thực hiện nhiều thao tác (nếu đúng thì một thao tác, sai - một thao tác khác). Như tôi đã viết ở trên, điều này không tốt và nên tránh nếu có thể.

  7. Nếu một phương thức có số lượng lớn các đối số đến (giá trị cực đại là 7, nhưng bạn nên nghĩ về nó sau 2-3), bạn cần nhóm một số đối số vào một đối tượng riêng biệt.

  8. Nếu có một số phương thức tương tự (quá tải) thì các tham số tương tự phải được truyền theo cùng một thứ tự: điều này làm tăng khả năng đọc và khả năng sử dụng.

  9. Khi bạn truyền tham số cho một phương thức, bạn phải chắc chắn rằng tất cả chúng sẽ được sử dụng, nếu không thì đối số để làm gì? Cắt nó ra khỏi giao diện và thế là xong.

  10. try/catchBản chất của nó trông không đẹp lắm, do đó, một động thái tốt là chuyển nó sang một phương thức riêng biệt trung gian (phương pháp xử lý các ngoại lệ):

    public void exceptionHandling(SomeObject obj) {
        try {
            someMethod(obj);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
Tôi đã nói về việc lặp lại mã ở trên, nhưng tôi sẽ thêm nó vào đây: Nếu chúng ta có một vài phương thức lặp lại các phần của mã, chúng ta cần chuyển nó sang một phương thức riêng, điều này sẽ làm tăng tính gọn nhẹ của cả phương thức và lớp học. Và đừng quên những cái tên chính xác. Tôi sẽ cho bạn biết chi tiết về cách đặt tên chính xác của các lớp, giao diện, phương thức và biến trong phần tiếp theo của bài viết. Và đó là tất cả những gì tôi có cho ngày hôm nay. Quy tắc viết code: từ tạo hệ thống đến làm việc với đối tượng - 9Quy tắc mã: sức mạnh của việc đặt tên phù hợp, nhận xét tốt và xấu
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION