Các chức năng chỉ gọi các chức năng khác. Đây có phải là một thực hành tốt?


13

Tôi hiện đang làm việc trên một tập hợp các báo cáo có nhiều phần khác nhau (tất cả đều yêu cầu định dạng khác nhau) và tôi đang cố gắng tìm ra cách tốt nhất để cấu trúc mã của mình. Các báo cáo tương tự chúng tôi đã thực hiện trong quá khứ có các hàm rất lớn (hơn 200 dòng) thực hiện tất cả các thao tác và định dạng dữ liệu cho báo cáo, sao cho quy trình công việc trông giống như sau:

DataTable reportTable = new DataTable();
void RunReport()
{
    reportTable = DataClass.getReportData();
    largeReportProcessingFunction();
    outputReportToUser();
}

Tôi muốn có thể chia các hàm lớn này thành các phần nhỏ hơn, nhưng tôi sợ rằng cuối cùng tôi sẽ có hàng tá các chức năng không thể tái sử dụng và một chức năng "làm mọi thứ ở đây" tương tự mà công việc duy nhất của nó là gọi tất cả các hàm nhỏ hơn, như vậy:

void largeReportProcessingFunction()
{
    processSection1HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    processSection1SummaryTableData();
    calculateSection1SummaryTableTotalRow();
    formatSection1SummaryTableDisplay();
    processSection1FooterData();
    getSection1FooterSummaryTotals();
    formatSection1FooterDisplay();

    processSection2HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    calculateSection1HeaderAverages();
    ...
}

Hoặc, nếu chúng ta tiến thêm một bước:

void largeReportProcessingFunction()
{
    callAllSection1Functions();

    callAllSection2Functions();

    callAllSection3Functions();
    ...        
}

Đây thực sự là một giải pháp tốt hơn? Từ quan điểm tổ chức, tôi cho rằng nó là (tức là mọi thứ đều có tổ chức hơn nhiều so với trước đây), nhưng về khả năng đọc mã tôi không chắc chắn (chuỗi chức năng lớn chỉ gọi các chức năng khác).

Suy nghĩ?


Vào cuối ngày ... nó có dễ đọc hơn không? Vâng, vì vậy nó là một giải pháp tốt hơn.
sylvanaar

Câu trả lời:


18

Điều này thường được gọi là phân rã chức năng và nói chung là một điều tốt để làm, nếu được thực hiện đúng.

Ngoài ra, việc thực hiện một chức năng nên nằm trong một mức độ trừu tượng duy nhất. Nếu bạn thực hiện largeReportProcessingFunction, thì vai trò của nó là xác định các bước xử lý khác nhau sẽ được thực hiện theo thứ tự nào. Việc thực hiện từng bước đó là trên một lớp trừu tượng bên dưới vàlargeReportProcessingFunction không nên phụ thuộc trực tiếp vào nó.

Xin lưu ý rằng đây là một lựa chọn tồi cho việc đặt tên:

void largeReportProcessingFunction() {
    callAllSection1Functions();
    callAllSection2Functions();
    callAllSection3Functions();
    ...        
}

Bạn thấy callAllSection1Functionslà một cái tên không thực sự cung cấp một sự trừu tượng, bởi vì nó không thực sự nói nó làm , mà là nó làm như thế nào . Nó nên được gọi processSection1thay thế hoặc bất cứ điều gì thực sự có ý nghĩa trong bối cảnh.


6
Tôi đồng ý với hiệu trưởng với câu trả lời này, ngoại trừ tôi không thể thấy "processSection1" là một cái tên có ý nghĩa như thế nào. Nó thực tế tương đương với "callAllSection1Fifts".
Eric King

2
@EricKing: callAllSection1Functionsrò rỉ chi tiết về việc thực hiện. Từ góc nhìn bên ngoài, việc processSection1trì hoãn công việc của nó thành "tất cả các chức năng của phần 1" không quan trọng hay liệu nó có thực hiện trực tiếp hay liệu nó sử dụng pixie-Dust để triệu tập một con kỳ lân sẽ thực hiện công việc thực tế. Tất cả những gì thực sự quan trọng là, sau khi gọi đến processSection1, phần 1 có thể được xem xét xử lý . Tôi đồng ý rằng xử lý là một tên chung, nhưng nếu bạn có sự gắn kết cao (mà bạn nên luôn luôn phấn đấu) thì bối cảnh của bạn thường đủ hẹp để phân tán nó.
back2dos

2
Tất cả những gì tôi muốn nói là, các phương thức gọi là "processXXX" hầu như luôn là những cái tên vô dụng, khủng khiếp. Tất cả các phương thức 'xử lý' mọi thứ, do đó, đặt tên cho nó là 'processXXX' cũng hữu ích như đặt tên cho nó là 'doS SomethingWithXXX' hoặc 'thao tácXXX' và tương tự. Hầu như luôn luôn có một tên tốt hơn cho các phương thức có tên 'processXXX'. Từ quan điểm thực tế, nó hữu ích (vô dụng) như 'callAllSection1Fifts'.
Eric King

4

Bạn nói rằng bạn đang sử dụng C #, vì vậy tôi tự hỏi liệu bạn có thể xây dựng một hệ thống phân cấp lớp có cấu trúc lợi dụng thực tế là bạn đang sử dụng ngôn ngữ hướng đối tượng không? Ví dụ, nếu việc chia báo cáo thành các phần có ý nghĩa, hãy có một Sectionlớp và mở rộng nó cho các yêu cầu cụ thể của khách hàng.

Có thể có ý nghĩa khi nghĩ về nó như thể bạn đang tạo một GUI không tương tác. Hãy suy nghĩ về các đối tượng bạn có thể tạo như thể chúng là các thành phần GUI khác nhau. Hãy tự hỏi một báo cáo cơ bản sẽ như thế nào? Làm thế nào về một phức tạp? Các điểm mở rộng nên ở đâu?


3

Nó phụ thuộc. Nếu tất cả các chức năng bạn đang tạo thực sự là một đơn vị công việc thì không sao, nếu chúng chỉ là các dòng 1-15, 16-30, 31-45 thì chức năng gọi của chúng là xấu. Các hàm dài không phải là xấu, nếu chúng làm một việc, nếu bạn có 20 bộ lọc cho báo cáo của mình có chức năng xác thực kiểm tra tất cả hai mươi sẽ dài. Điều đó thật tốt, phá vỡ nó bằng bộ lọc hoặc các nhóm bộ lọc chỉ là tăng thêm độ phức tạp mà không có lợi ích thực sự.

Có nhiều chức năng riêng tư cũng khiến việc kiểm tra đơn vị tự động trở nên khó khăn hơn, vì vậy một số người sẽ cố gắng tránh nó vì lý do đó, nhưng theo tôi thì đây là lý do khá kém vì nó có xu hướng tạo ra một thiết kế phức tạp hơn để phù hợp với thử nghiệm.


3
Theo kinh nghiệm của tôi, các chức năng dài xấu xa.
Bernard

Nếu ví dụ của bạn là ngôn ngữ OO, tôi sẽ tạo một lớp bộ lọc cơ sở và mỗi trong số 20 bộ lọc sẽ ở các lớp dẫn xuất khác nhau. Câu lệnh chuyển đổi sẽ được thay thế bằng lệnh gọi tạo để có bộ lọc phù hợp. Mỗi chức năng làm một việc và tất cả đều nhỏ.
DXM

3

Vì bạn đang sử dụng C #, hãy thử và suy nghĩ nhiều hơn trong các đối tượng. Có vẻ như bạn vẫn đang mã hóa theo thủ tục bằng cách có các biến lớp "toàn cầu" mà các hàm tiếp theo phụ thuộc vào.

Phá vỡ logic của bạn trong các chức năng riêng biệt chắc chắn tốt hơn là có tất cả logic trong một chức năng. Tôi cá là bạn có thể đi xa hơn bằng cách tách dữ liệu mà các chức năng thực hiện bằng cách tách nó ra trong một số đối tượng.

Cân nhắc việc có một lớp ReportBuilder nơi bạn có thể chuyển vào dữ liệu báo cáo. Nếu bạn có thể chia ReportBuilder thành các lớp riêng biệt, hãy làm điều này.


2
Thủ tục phục vụ một chức năng hoàn toàn tốt.
DeadMG

1

Đúng.

Nếu bạn có một đoạn mã cần được sử dụng ở nhiều vị trí riêng. Mặt khác, nếu bạn phải thay đổi chức năng, bạn sẽ cần thực hiện thay đổi ở nhiều nơi. Điều này không chỉ làm tăng công việc mà còn tăng nguy cơ lỗi xuất hiện khi mã được thay đổi ở một nơi chứ không phải ở nơi khác hoặc nơi được giới thiệu ở một nơi chứ không phải ở nơi khác.

Nó cũng giúp làm cho phương thức dễ đọc hơn nếu tên phương thức mô tả được sử dụng.


1

Việc bạn sử dụng đánh số để phân biệt các hàm cho thấy rằng có những vấn đề tiềm ẩn với sự trùng lặp hoặc một lớp làm quá nhiều. Chia một chức năng lớn thành nhiều chức năng nhỏ hơn có thể là một bước hữu ích trong việc tái cấu trúc sao chép, nhưng nó không phải là chức năng cuối cùng. Chẳng hạn, bạn tạo hai hàm:

processSection1HeaderData();
processSection2HeaderData();

Không có bất kỳ sự tương đồng trong mã được sử dụng để xử lý các tiêu đề phần? Bạn có thể trích xuất một chức năng chung cho cả hai bằng cách tham số hóa sự khác biệt?


Đây không thực sự là mã sản xuất, vì vậy tên hàm chỉ là ví dụ (trong sản xuất, tên hàm được mô tả nhiều hơn). Nói chung, không, không có sự tương đồng giữa các phần khác nhau (mặc dù khi có sự tương đồng, tôi cố gắng sử dụng lại mã càng nhiều càng tốt).
Eric C.

1

Việc chia một hàm thành các hàm con logic là hữu ích, ngay cả khi các hàm con không được sử dụng lại - nó chia nó thành các khối ngắn, dễ hiểu. Nhưng nó phải được thực hiện bởi các đơn vị logic, không chỉ n # dòng. Làm thế nào bạn nên phá vỡ nó sẽ phụ thuộc vào mã của bạn, các chủ đề phổ biến sẽ là các vòng lặp, điều kiện, hoạt động trên cùng một đối tượng; về cơ bản bất cứ điều gì mà bạn có thể mô tả như một nhiệm vụ riêng biệt.

Vì vậy, vâng, đó là một phong cách tốt - điều này nghe có vẻ lạ, nhưng với mô tả tái sử dụng hạn chế của bạn, tôi khuyên bạn nên suy nghĩ cẩn thận về việc tái sử dụng ATTEMPTING. Hãy chắc chắn rằng việc sử dụng là thực sự giống hệt nhau, và không chỉ trùng hợp như nhau. Cố gắng xử lý tất cả các trường hợp trong cùng một khối thường là cách bạn kết thúc với chức năng vô duyên lớn ở vị trí đầu tiên.


0

Tôi nghĩ rằng điều này chủ yếu là sở thích cá nhân. Nếu các chức năng của bạn sẽ được sử dụng lại (và bạn có chắc chắn rằng bạn không thể làm cho chúng chung chung và có thể tái sử dụng hơn không?) Tôi chắc chắn sẽ nói tách chúng ra. Tôi cũng đã nghe nói rằng một nguyên tắc tốt là không có chức năng hoặc phương thức nào nên có nhiều hơn 2-3 màn hình mã trong đó. Vì vậy, nếu chức năng lớn của bạn cứ lặp đi lặp lại, nó có thể làm cho mã của bạn dễ đọc hơn để phân tách nó.

Bạn không đề cập đến công nghệ nào bạn đang sử dụng để phát triển các báo cáo này. Có vẻ như bạn có thể đang sử dụng C #. Đúng không? Nếu vậy, bạn cũng có thể coi lệnh #region như một cách thay thế nếu bạn không muốn chia chức năng của mình thành các hàm nhỏ hơn.


Có, tôi đang làm việc tại C #. Có thể tôi sẽ có thể sử dụng lại một số chức năng, nhưng đối với nhiều báo cáo này, các phần khác nhau có dữ liệu và bố cục rất khác nhau (yêu cầu của khách hàng).
Eric C.

1
+1 ngoại trừ các khu vực gợi ý. Tôi sẽ không sử dụng các khu vực.
Bernard

@Bernard - Bất kỳ lý do cụ thể? Tôi giả sử người hỏi sẽ chỉ xem xét sử dụng #regions nếu anh ta đã loại trừ việc chia tách chức năng.
Joshua Carmody

2
Các vùng có xu hướng ẩn mã và có thể bị lạm dụng (tức là các vùng lồng nhau). Tôi thích nhìn thấy tất cả các mã cùng một lúc trong một tệp mã. Nếu tôi cần tách mã trực quan trong một tệp, tôi sử dụng một dòng dấu gạch chéo về phía trước.
Bernard

2
Các khu vực thường là một dấu hiệu mã cần tái cấu trúc
coder
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.