Cách thân thiện nhất với con người để đặt hàng các định nghĩa phương thức lớp?


38

Trong bất kỳ định nghĩa lớp cụ thể nào, tôi đã thấy các định nghĩa phương thức được sắp xếp theo nhiều cách khác nhau: theo thứ tự chữ cái, theo thời gian dựa trên cách sử dụng phổ biến nhất, được sắp xếp theo thứ tự bảng chữ cái, bảng chữ cái với các biểu đồ và setters được nhóm lại với nhau, v.v. Khi tôi bắt đầu viết một lớp mới, Tôi có xu hướng chỉ gõ mọi thứ vào, sau đó sắp xếp lại khi tôi viết xong cả lớp. Trên lưu ý đó, tôi đã có ba câu hỏi:

  1. Có vấn đề gì không?
  2. Có một thứ tự "tốt nhất"?
  3. Tôi đoán là không có, vậy ưu và nhược điểm của các chiến lược đặt hàng khác nhau là gì?

1
Bạn không thực sự mong đợi mọi người cắt / dán mã chỉ để sắp xếp lại các phương thức. Nếu IDE làm điều đó tự động, thì tốt. Nếu không, thật khó để thực thi.
Phản ứng

Để làm rõ, câu hỏi của tôi là về khả năng đọc / khả năng sử dụng, không phải cú pháp.
Johntron

Câu trả lời:


52

Trong một số ngôn ngữ lập trình, thứ tự có vấn đề vì bạn không thể sử dụng mọi thứ cho đến khi chúng được khai báo. Nhưng việc chặn rằng, đối với hầu hết các ngôn ngữ, nó không quan trọng đối với trình biên dịch. Vì vậy, sau đó, bạn còn lại với nó quan trọng với con người.

Câu nói yêu thích của Martin Fowler của tôi là: Any fool can write code that a computer can understand. Good programmers write code that humans can understand.Vì vậy, tôi muốn nói rằng việc sắp xếp thứ tự lớp học của bạn nên phụ thuộc vào điều gì giúp con người dễ hiểu.

Cá nhân tôi thích cách đối xử từ từ mà Bob Martin đưa ra trong Clean Codecuốn sách của mình . Các biến thành viên ở đầu lớp, sau đó là constructor, sau đó là tất cả các phương thức khác. Và bạn ra lệnh cho các phương thức gần nhau với cách chúng được sử dụng trong lớp (thay vì tự ý đặt tất cả công khai rồi riêng tư sau đó được bảo vệ). Ông gọi nó là tối thiểu hóa "khoảng cách dọc" hoặc một cái gì đó tương tự (hiện tại không có cuốn sách nào cho tôi).

Chỉnh sửa:

Ý tưởng cơ bản của "khoảng cách dọc" là bạn muốn tránh làm cho mọi người nhảy xung quanh mã nguồn của bạn chỉ để hiểu nó. Nếu mọi thứ có liên quan, họ nên gần nhau hơn. Những thứ không liên quan có thể xa nhau hơn.

Chương 5 của Clean Code (cuốn sách tuyệt vời, btw) đi sâu vào rất nhiều chi tiết về cách ông Martin gợi ý mã đặt hàng. Ông gợi ý rằng việc đọc mã nên hoạt động giống như đọc một bài báo: các chi tiết cấp cao xuất hiện trước (ở trên cùng) và bạn sẽ có được nhiều chi tiết hơn khi bạn đọc xuống. Ông nói, "Nếu một chức năng gọi một chức năng khác, chúng nên được đóng theo chiều dọc và người gọi nên ở trên mức trung bình, nếu có thể." Ngoài ra, các khái niệm liên quan nên gần nhau.

Vì vậy, đây là một ví dụ giả định rất tệ theo nhiều cách (thiết kế OO kém; không bao giờ sử dụng doubletiền) nhưng minh họa ý tưởng:

public class Employee {
  ...
  public String getEmployeeId() { return employeeId; }
  public String getFirstName() { return firstName; }
  public String getLastName() { return lastName; }

  public double calculatePaycheck() {
    double pay = getSalary() / PAY_PERIODS_PER_YEAR;
    if (isEligibleForBonus()) {
      pay += calculateBonus();
    }
    return pay;
  }

  private double getSalary() { ... }

  private boolean isEligibleForBonus() {
    return (isFullTimeEmployee() && didCompleteBonusObjectives());
  }

  public boolean isFullTimeEmployee() { ... }
  private boolean didCompleteBonusObjectives() { ... }
  private double calculateBonus() { ... }
}

Các phương thức được sắp xếp sao cho chúng gần với các phương thức gọi chúng, làm việc theo cách của chúng tôi từ trên xuống. Nếu chúng ta đã đặt tất cả các privatephương thức bên dưới các phương thức public, thì bạn sẽ phải nhảy nhiều hơn để theo dòng chảy của chương trình.

getFirstNamegetLastNamecó liên quan về mặt khái niệm (và getEmployeeIdcó lẽ là quá), vì vậy chúng gần nhau. Chúng ta có thể di chuyển tất cả chúng xuống phía dưới, nhưng chúng ta sẽ không muốn nhìn thấy getFirstNameở phía trên và getLastNamephía dưới.

Hy vọng điều này cung cấp cho bạn những ý tưởng cơ bản. Nếu bạn quan tâm đến loại điều này, tôi thực sự khuyên bạn nên đọc Clean Code.


Tôi cần phải biết cách đặt setters và getters của các biến thể hiện. Nó nên đến ngay sau khi người xây dựng lớp hoặc ở dưới cùng của lớp?
srinivas

Cá nhân tôi thích chúng ở đầu sau khi xây dựng. Nhưng nó không thực sự quan trọng; Tôi muốn đề xuất sự nhất quán trong dự án của bạn và với nhóm của bạn là một cách tốt để quyết định.
Allan

Không nên calculateBonus()đến trước isFullTimeEmployee()didCompleteBonusObjectives()?
winklerrr

@winklerrr Tôi có thể thấy một đối số cho điều đó. Tôi đặt chúng ở nơi tôi đã làm bởi vì isFullTimeEmployeedidCompleteBonusObjectivesđược sử dụng bởi isEligibleForBonusvì vậy chúng phải ở gần nó. Nhưng bạn có khả năng có thể di chuyển calculateBonuslên để đặt nó gần hơn với nơi nó được gọi. Thật không may, vì bạn có các chức năng gọi các chức năng, cuối cùng bạn sẽ gặp phải các vấn đề (như một chức năng được chia sẻ bởi nhiều người khác) trong đó không có thứ tự hoàn hảo. Sau đó, nó tùy thuộc vào sự đánh giá tốt nhất của bạn. Tôi khuyên bạn nên giữ các lớp và chức năng nhỏ để giảm thiểu những vấn đề đó.
Allan

2

Tôi thường đặt hàng các phương pháp của tôi theo quan hệ và thứ tự sử dụng.

Tham gia một lớp kết nối mạng, tôi sẽ nhóm tất cả các phương thức TCP lại với nhau, sau đó tất cả các phương thức UDP lại với nhau. TCP bên trong tôi sẽ có phương thức thiết lập là đầu tiên, có thể gửi một tin nhắn đã cho thứ hai và đóng socket tcp là thứ ba.

Rõ ràng không phải tất cả các lớp sẽ phù hợp với mô hình đó, nhưng đó là quy trình làm việc chung của tôi.

Tôi làm theo cách đó để gỡ lỗi nhiều hơn bất cứ điều gì khác, khi tôi gặp vấn đề và tôi muốn chuyển sang phương thức này, tôi không nghĩ nó được đánh vần như thế nào, tôi nghĩ nó chịu trách nhiệm gì và đi đến phần đó.

Cách này đặc biệt có ý nghĩa đối với bên thứ ba xem / sử dụng mã của bạn khi được nhóm lại với nhau và sẽ tuân theo thứ tự được sử dụng, do đó mã họ sẽ viết với lớp của bạn sẽ theo cùng cấu trúc với lớp.

Có liên quan gì không?

cho dễ đọc, chắc chắn.

khác với điều đó không thực sự, chỉ trong trường hợp một số ngôn ngữ nhất định bạn không thể gọi phương thức trừ khi được xác định ở trên nơi được gọi là v.v.


0

Lý tưởng nhất là các lớp học của bạn nhỏ đến mức không thành vấn đề. Nếu bạn chỉ có một tá phương thức và trình soạn thảo hoặc IDE của bạn hỗ trợ gấp, bạn không gặp vấn đề gì, vì toàn bộ danh sách các phương thức phù hợp với 12 dòng.

Không, sự khác biệt cấp cao nhất phải là công khai so với riêng tư. Liệt kê các phương thức công khai trước: đây là những gì mọi người sẽ tìm kiếm nhiều nhất, bởi vì chúng xác định cách giao diện lớp của bạn với phần còn lại của cơ sở mã.

Sau đó, bên trong mỗi phương thức này, có ý nghĩa nhất để nhóm các phương thức theo chức năng: hàm tạo và hàm hủy trong một khối, getters / setters trong khối khác, quá tải toán tử, phương thức tĩnh và phần còn lại của bó. Trong C ++, tôi muốn theo operator=sát các hàm tạo, bởi vì nó liên quan chặt chẽ với hàm tạo sao chép và cũng vì tôi muốn có thể nhanh chóng phát hiện xem tất cả (hoặc không) của ctor mặc định, sao chép ctor, toán tử = và dtor hiện hữu.


-1

tl; dr

Chỉ khi ngôn ngữ bạn đang làm việc yêu cầu một thứ tự cụ thể. Ngoài ra, việc đặt hàng là tùy thuộc vào bạn. Chọn một hệ thống phù hợp và có ý nghĩa, và cố gắng gắn bó với nó càng nhiều càng tốt.


1. Có vấn đề gì không?

Chỉ khi ngôn ngữ bạn đang làm việc cần phải có một chức năng được xác định sớm hơn trong tệp so với nơi nó được gọi, chẳng hạn như trong ví dụ này:

void funcA()
{
   funcB();
}

void funcB()
{
   //do something interesting...
}

bạn sẽ gặp lỗi vì bạn gọi funcB()trước khi sử dụng. Tôi nghĩ rằng đây là một vấn đề trong PL / SQL và có thể cả C, nhưng bạn có thể có các khai báo chuyển tiếp như:

void funcB();

void funcA()
{
   funcB();
}

void funcB()
{
   //do something interesting...
}

Đây là tình huống duy nhất tôi có thể nghĩ đến nếu đơn đặt hàng là "sai", bạn thậm chí sẽ không thể biên dịch.

Nếu không, bạn luôn có thể đặt hàng lại theo cách bạn muốn. Bạn thậm chí có thể viết một công cụ để làm điều đó cho bạn (nếu bạn không thể tìm thấy một công cụ ngoài đó).

2. Có một thứ tự "tốt nhất"?

Nếu ngôn ngữ / môi trường không có yêu cầu đặt hàng, thì thứ tự "tốt nhất" là thứ phù hợp nhất với bạn . Đối với tôi, tôi muốn có tất cả các getters / setters cùng nhau, thường là vào đầu lớp (nhưng sau các hàm tạo / khởi tạo tĩnh), và sau đó là các phương thức riêng tư, sau đó được bảo vệ, sau đó công khai. Trong mỗi nhóm dựa trên phạm vi thường không có thứ tự, mặc dù các phương thức quá tải tôi cố gắng giữ cùng nhau, theo thứ tự số lượng tham số. Tôi cũng cố gắng giữ các phương thức có chức năng liên quan với nhau, mặc dù đôi khi tôi phải phá vỡ thứ tự dựa trên phạm vi của mình để làm như vậy; và đôi khi cố gắng duy trì trật tự dựa trên phạm vi phá vỡ chức năng theo nhóm. Và IDE của tôi có thể cho tôi một khung nhìn phác thảo bảng chữ cái, vì vậy điều đó cũng tốt. ;)

Một số ngôn ngữ, như C # có khả năng nhóm mã theo "vùng" không ảnh hưởng đến quá trình biên dịch nhưng có thể giúp giữ các chức năng liên quan với nhau dễ dàng hơn, sau đó ẩn / hiển thị chúng với IDE. Như MainMa đã chỉ ra , có một số người coi đây là một thực hành tồi. Tôi đã thấy các ví dụ tốt và xấu về các khu vực được sử dụng theo cách này, vì vậy nếu bạn sẽ đi theo cách này, hãy cẩn thận.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.