Có một điều như là có quá nhiều chức năng / phương thức riêng tư?


63

Tôi hiểu tầm quan trọng của mã tài liệu tốt. Nhưng tôi cũng hiểu tầm quan trọng của mã tự viết tài liệu . Càng dễ đọc trực quan một chức năng cụ thể, chúng ta càng có thể di chuyển nhanh hơn trong quá trình bảo trì phần mềm.

Như đã nói, tôi thích tách các chức năng lớn thành các chức năng nhỏ khác. Nhưng tôi làm như vậy đến mức một lớp có thể có tới năm trong số chúng chỉ để phục vụ một phương thức công khai. Bây giờ nhân năm phương thức riêng tư với năm phương thức công khai và bạn có được khoảng hai mươi lăm phương thức ẩn có thể sẽ chỉ được gọi một lần bởi những phương thức công khai đó.

Chắc chắn, giờ đây việc đọc các phương thức công khai đó trở nên dễ dàng hơn, nhưng tôi không thể không nghĩ rằng có quá nhiều chức năng là thực tiễn tồi.

[Biên tập]

Mọi người đã hỏi tôi tại sao tôi nghĩ rằng có quá nhiều chức năng là thực hành tồi.

Câu trả lời đơn giản: đó là một cảm giác ruột.

Tôi tin rằng, không một chút nào, được hỗ trợ bởi bất kỳ giờ kinh nghiệm kỹ thuật phần mềm nào. Đó chỉ là một sự không chắc chắn đã cho tôi một "khối nhà văn", nhưng đối với một lập trình viên.

Trước đây, tôi chỉ lập trình các dự án cá nhân. Gần đây tôi đã chuyển sang các dự án dựa trên nhóm. Bây giờ, tôi muốn đảm bảo rằng những người khác có thể đọc và hiểu mã của tôi.

Tôi không chắc những gì sẽ cải thiện mức độ dễ đọc. Một mặt, tôi đã nghĩ đến việc tách một chức năng lớn thành các chức năng nhỏ khác với các tên dễ hiểu. Nhưng có một mặt khác của tôi nói rằng nó chỉ là dư thừa.

Vì vậy, tôi đang yêu cầu điều này soi sáng bản thân để chọn đúng con đường.

[Biên tập]

Dưới đây, tôi bao gồm hai phiên bản về cách tôi có thể giải quyết vấn đề của mình. Cái đầu tiên giải quyết nó bằng cách không tách các đoạn mã lớn. Cái thứ hai làm những việc riêng biệt.

Phiên bản đầu tiên:

public static int Main()
{
    // Displays the menu.
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

Phiên bản thứ hai:

public static int Main()
{
    DisplayMenu();
}

private static void DisplayMenu()
{
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

Trong các ví dụ trên, hàm sau gọi một hàm sẽ chỉ được sử dụng một lần trong toàn bộ thời gian chạy của chương trình.

Lưu ý: mã ở trên được khái quát hóa, nhưng nó có cùng bản chất với vấn đề của tôi.

Bây giờ, đây là câu hỏi của tôi: cái nào? Tôi chọn cái đầu tiên, hay cái thứ hai?


1
"Có quá nhiều chức năng là thực hành xấu."? Tại sao? Vui lòng cập nhật câu hỏi của bạn để giải thích tại sao điều này có vẻ xấu với bạn.
S.Lott

Tôi khuyên bạn nên đọc khái niệm về 'tính cố kết của lớp' - Bob Martin thảo luận về điều này trong 'Mã sạch'.
JMᴇᴇ

Thêm vào các câu trả lời khác, phân tách và đặt tên, nhưng tất cả chúng ta hãy nhớ rằng, khi một lớp phát triển, các biến thành viên của nó ngày càng giống như các biến toàn cục. Một cách để cải thiện vấn đề (ngoài các giải pháp tốt hơn như tái cấu trúc ;-)) là tách các phương thức riêng tư nhỏ đó thành các hàm độc lập nếu có thể (trong đó chúng không cần truy cập vào nhiều trạng thái). Một số ngôn ngữ cho phép các chức năng ngoài lớp, những ngôn ngữ khác có các lớp tĩnh. Theo cách này, bạn có thể hiểu chức năng đó một cách cô lập.
Pablo H

Nếu ai đó có thể cho tôi biết tại sao "câu trả lời này không hữu ích", tôi sẽ rất biết ơn. Một người luôn có thể học hỏi. :-)
Pablo H

@PabloH Câu trả lời bạn cung cấp mang lại sự quan sát và hiểu biết sâu sắc cho OP, tuy nhiên nó không thực sự trả lời câu hỏi đã được hỏi. Tôi chuyển nó vào lĩnh vực bình luận.
maple_shaft

Câu trả lời:


36

Bây giờ nhân năm phương thức riêng tư với năm phương thức công khai và bạn có khoảng hai mươi lăm phương thức ẩn có thể sẽ chỉ được gọi một lần bởi những phương thức công khai đó.

Đây là cái được gọi là Encapsulation , tạo ra Trừu tượng hóa kiểm soát ở cấp độ cao hơn. Đây là một điều tốt.

Điều này có nghĩa là bất cứ ai đọc mã của bạn, khi họ nhận được để các startTheEngine()phương pháp trong mã của bạn, có thể bỏ qua tất cả các chi tiết mức độ thấp hơn như openIgnitionModule(), turnDistributorMotor(), sendSparksToSparkPlugs(), injectFuelIntoCylinders(), activateStarterSolenoid(), và tất cả các khu phức hợp, nhỏ, các chức năng khác mà phải được chạy để tạo điều kiện cho các chức năng lớn hơn, trừu tượng hơn của startTheEngine().

Trừ khi vấn đề bạn đang giải quyết trong mã của bạn liên quan trực tiếp đến một trong những thành phần đó, những người duy trì mã có thể tiếp tục, bỏ qua chức năng đóng gói, đóng hộp cát.

Điều này cũng có thêm lợi thế là làm cho mã của bạn dễ kiểm tra hơn. . Chẳng hạn, tôi có thể viết một trường hợp turnAlternatorMotor(int revolutionRate)thử nghiệm và kiểm tra chức năng của nó hoàn toàn độc lập với các hệ thống khác. Nếu có vấn đề với chức năng đó và đầu ra không như tôi mong đợi, thì tôi biết vấn đề là gì.

Mã không được chia thành các thành phần khó kiểm tra hơn nhiều. Đột nhiên, những người bảo trì của bạn chỉ nhìn vào sự trừu tượng thay vì có thể đào sâu vào các thành phần có thể đo lường được.

Lời khuyên của tôi là hãy tiếp tục làm những gì bạn đang làm vì mã của bạn sẽ mở rộng, dễ bảo trì và có thể được sử dụng và cập nhật trong nhiều năm tới.


3
Vì vậy, bạn tin rằng 'đóng gói' tốt để cung cấp logic cho toàn bộ lớp chỉ được gọi ở một nơi?
Steven Jeuris

9
@Steven Jeuris: Miễn là các chức năng đó được đặt ở chế độ riêng tư, tôi thấy không có vấn đề gì với nó; trong thực tế, tôi thấy rằng, như đã nêu trong câu trả lời này, dễ đọc hơn. Việc hiểu một chức năng công cộng cấp cao sẽ dễ dàng hơn nhiều khi chỉ gọi một loạt các chức năng riêng tư cấp thấp (dĩ nhiên đã được đặt tên thích hợp). Chắc chắn, tất cả các mã đó có thể đã được đặt vào hàm cấp cao, nhưng sau đó sẽ mất nhiều thời gian hơn để hiểu mã khi bạn phải xem xét nhiều hơn về nó tại cùng một nơi.
gablin

2
@gablin: các chức năng riêng tư vẫn có thể truy cập được trong toàn bộ lớp. Trong bài viết này (dường như gây tranh cãi) tôi thảo luận về việc 'khả năng đọc toàn cầu' bị mất khi có quá nhiều chức năng. Trên thực tế, một nguyên tắc chung bên cạnh đó là các chức năng chỉ nên làm một điều, đó là bạn không nên có quá nhiều. Bạn chỉ có được "khả năng đọc cục bộ" bằng cách chia nhỏ chỉ trong thời gian ngắn, cũng có thể đạt được với các nhận xét đơn giản và "đoạn mã". Ok, mã nên được tự ghi lại, nhưng ý kiến ​​không bị lỗi thời!
Steven Jeuris

1
@Steven - Điều gì dễ hiểu hơn? myString.replaceAll(pattern, originalData);hoặc tôi dán toàn bộ phương thức thay thế trong mã của mình, bao gồm mảng char dự phòng đại diện cho đối tượng chuỗi bên dưới mỗi lần tôi cần sử dụng chức năng của Chuỗi?
jmort253

7
@Steven Jeuris: Bạn có một điểm. Nếu một lớp có quá nhiều chức năng (công khai hoặc riêng tư), thì có thể toàn bộ lớp đang cố gắng làm quá nhiều. Trong những trường hợp như vậy, có thể tốt hơn khi chia nó thành nhiều lớp nhỏ hơn, giống như bạn sẽ tách một hàm quá lớn thành nhiều hàm nhỏ hơn.
gablin

22

Có và không. Nguyên lý cơ bản là: một phương pháp nên làm một việc và chỉ một việc

Thật đúng khi "chia nhỏ" phương thức của bạn thành các phương thức nhỏ hơn. Sau đó, một lần nữa, các phương thức đó nên tối ưu đủ chung để không chỉ phục vụ phương pháp "lớn" đó mà còn có thể tái sử dụng ở những nơi khác. Trong một số trường hợp, một phương thức sẽ tuân theo một cấu trúc cây đơn giản, như Phương thức khởi tạo có tên là LaunchizePlugins, InitializeInterface, v.v.

Nếu bạn có một phương thức / lớp thực sự lớn thì đó thường là một dấu hiệu cho thấy nó đang hoạt động rất nhiều và bạn cần thực hiện một số phép tái cấu trúc để phá vỡ "blob". Perphaps ẩn một số phức tạp trong một lớp khác dưới một sự trừu tượng hóa, và sử dụng tiêm phụ thuộc


1
Thật tuyệt khi đọc không phải ai cũng tuân theo quy tắc 'một điều' một cách mù quáng! ; p Câu trả lời tốt đẹp!
Steven Jeuris

16
Tôi thường chia một phương thức lớn thành các phương thức nhỏ hơn mặc dù chúng sẽ chỉ phục vụ phương thức lớn hơn. Lý do để phân chia nó là vì nó trở nên dễ xử lý và dễ hiểu hơn, thay vì chỉ có một đốm mã khổng lồ (có thể tự hoặc không thể nhận xét tốt).
gablin

1
Điều đó cũng ổn trong một số trường hợp bạn thực sự không thể tránh được một phương pháp lớn. Chẳng hạn, nếu bạn có một phương thức Khởi tạo, bạn có thể gọi phương thức đó là LaunchizeSystems, InitializePlugins, v.v. Người ta phải luôn luôn tự kiểm tra xem việc tái cấu trúc là không cần thiết
Homde

1
Điều quan trọng về mọi phương thức (hoặc chức năng) là nó phải có giao diện khái niệm rõ ràng, đó là bản hợp đồng. Nếu bạn không thể xác định điều đó, nó sẽ gặp rắc rối và bạn đã bao thanh toán sai. (Tài liệu rõ ràng về hợp đồng có thể là một điều tốt - hoặc sử dụng một công cụ để thực thi nó, theo kiểu Eiffel - nhưng nó không quan trọng bằng việc có nó ở nơi đầu tiên.)
Donal Fellows

3
@gablin: lợi ích khác để xem xét: khi tôi phá vỡ mọi thứ lên vào chức năng nhỏ hơn, không nghĩ rằng "họ chỉ phục vụ một chức năng khác", suy nghĩ "họ chỉ phục vụ một chức năng khác cho bây giờ ". Đã có một vài lần mà việc bao thanh toán lại các phương thức lớn đã giúp tôi tiết kiệm được rất nhiều thời gian khi viết các phương thức khác trong tương lai vì các hàm nhỏ hơn thường chung chung và có thể sử dụng lại được.
GSto

17

Tôi nghĩ nói chung tốt hơn là sai ở phía quá nhiều chức năng hơn là quá ít. Hai trường hợp ngoại lệ cho quy tắc mà tôi đã thấy trong thực tế là cho các nguyên tắc DRYYAGNI . Nếu bạn có nhiều chức năng gần như giống hệt nhau, bạn nên kết hợp chúng và sử dụng các tham số để tránh lặp lại chính mình. Bạn cũng có thể có quá nhiều chức năng nếu bạn tạo chúng "chỉ trong trường hợp" và chúng không được sử dụng. Tôi thấy hoàn toàn không có gì sai khi có một chức năng chỉ được sử dụng một lần nếu nó thêm khả năng đọc và bảo trì.


10

Câu trả lời tuyệt vời của jmort253 đã truyền cảm hứng cho tôi theo dõi câu trả lời "có, nhưng"

Có nhiều phương pháp riêng tư nhỏ không phải là một điều xấu, nhưng hãy chú ý đến cảm giác khó chịu đó khiến bạn phải đặt câu hỏi ở đây :). Nếu bạn đến một điểm mà bạn đang viết các bài kiểm tra chỉ tập trung vào các phương thức riêng tư (bằng cách gọi trực tiếp hoặc bằng cách thiết lập một kịch bản sao cho một kịch bản được gọi theo một cách nhất định để bạn có thể kiểm tra kết quả) thì bạn có thể kiểm tra kết quả) bạn nên lùi lại một bước

Những gì bạn có thể có là một lớp mới với giao diện công cộng tập trung hơn đang cố gắng thoát ra. Tại thời điểm đó, bạn có thể muốn nghĩ về việc trích xuất một lớp ra để gói gọn một phần chức năng lớp hiện có của bạn hiện đang sống theo phương thức riêng tư. Bây giờ lớp ban đầu của bạn có thể sử dụng lớp mới của bạn như một phụ thuộc và bạn có thể viết các bài kiểm tra tập trung hơn vào lớp đó trực tiếp.


Tôi nghĩ rằng đây là một câu trả lời tuyệt vời và là một hướng mà mã chắc chắn có thể đi. Thực tiễn tốt nhất nói rằng sử dụng công cụ sửa đổi truy cập hạn chế nhất cung cấp cho bạn chức năng bạn cần. Nếu các phương thức không được sử dụng bên ngoài lớp, thì riêng tư có ý nghĩa nhất. Nếu nó có ý nghĩa hơn để làm cho các phương thức được công khai hoặc chuyển chúng sang một lớp khác để chúng có thể được sử dụng ở nơi khác, thì bằng mọi cách, điều đó cũng có thể được thực hiện. +1
jmort253

8

Hầu như mọi dự án OO mà tôi từng tham gia đều chịu thiệt hại nặng nề từ các lớp quá lớn và các phương pháp quá dài.

Khi tôi cảm thấy cần một bình luận giải thích phần tiếp theo của mã, sau đó tôi trích xuất phần đó thành một phương thức riêng biệt. Về lâu dài, điều này làm cho mã ngắn hơn. Những phương pháp nhỏ đó được sử dụng lại nhiều hơn bạn mong đợi ban đầu. Bạn bắt đầu nhìn thấy cơ hội để thu thập một loạt chúng vào một lớp riêng biệt. Chẳng mấy chốc bạn đang nghĩ về vấn đề của mình ở cấp độ cao hơn, và toàn bộ nỗ lực diễn ra nhanh hơn nhiều. Các tính năng mới dễ viết hơn, bởi vì bạn có thể xây dựng trên các lớp nhỏ mà bạn đã tạo từ các phương thức nhỏ đó.


7

Một lớp học với 5 phương thức công khai và 25 phương thức riêng tư dường như không lớn đối với tôi. Chỉ cần đảm bảo rằng các lớp của bạn có trách nhiệm được xác định rõ và đừng quá lo lắng về số lượng phương thức.

Điều đó nói rằng, các phương thức riêng tư đó nên tập trung vào một khía cạnh cụ thể của toàn bộ tác vụ, nhưng không phải theo cách "doS SomethingPart1", "doS SomethingPart2". Bạn chẳng đạt được gì nếu những phương thức riêng tư đó chỉ là những phần của một câu chuyện dài được phân chia tùy tiện, mỗi câu chuyện đều vô nghĩa bên ngoài toàn bộ bối cảnh.


+1 cho "Bạn chẳng đạt được gì nếu những phương thức riêng tư đó chỉ là những phần của một câu chuyện dài được chia tùy ý, mỗi câu chuyện đều vô nghĩa bên ngoài toàn bộ bối cảnh."
Mahdi

4

Trích từ Bộ luật sạch của R. Martin (trang 176):

Ngay cả các khái niệm cơ bản như loại bỏ sự trùng lặp, tính biểu cảm mã và SRP có thể được đưa ra quá xa. Trong nỗ lực làm cho các lớp và phương thức của chúng ta nhỏ đi, chúng ta có thể tạo ra quá nhiều lớp và phương thức nhỏ. Vì vậy, quy tắc này [tối thiểu hóa số lượng các lớp và phương thức] cho thấy rằng chúng ta cũng giữ cho hàm và số lớp của chúng ta ở mức thấp. Số lượng cao và phương pháp đôi khi là kết quả của chủ nghĩa giáo điều vô nghĩa.


3

Một số tùy chọn:

  1. Tốt rồi. Chỉ cần đặt tên cho các phương thức riêng tư cũng như bạn đặt tên cho các phương thức công khai của bạn. Và đặt tên cho phương pháp công khai của bạn tốt.

  2. Tóm tắt một số phương thức của trình trợ giúp này thành các lớp trình trợ giúp hoặc các mô đun trình trợ giúp. Và chắc chắn rằng các lớp / mô-đun trợ giúp này được đặt tên tốt và là bản tóm tắt vững chắc trong chính chúng.


1

Tôi không nghĩ đã từng có vấn đề về "quá nhiều phương thức riêng tư" nếu cảm thấy như vậy có lẽ đó là một triệu chứng của kiến ​​trúc mã kém.

Đây là cách tôi nghĩ về nó ... Nếu kiến ​​trúc được thiết kế tốt, nhìn chung thì rõ ràng mã X nên ở đâu. Có thể chấp nhận được nếu có một vài trường hợp ngoại lệ cho vấn đề này - nhưng chúng cần phải thực sự đặc biệt nếu không cấu trúc lại kiến ​​trúc để xử lý chúng theo tiêu chuẩn.

Nếu vấn đề là khi mở lớp của bạn, nó có đầy đủ các phương thức riêng tư đang chia các phần lớn hơn thành các đoạn mã nhỏ hơn, thì có lẽ đây là một triệu chứng của kiến ​​trúc bị thiếu. Thay vì các lớp và kiến ​​trúc, vùng mã này đã được triển khai theo cách thủ tục trong một lớp.

Tìm sự cân bằng ở đây phụ thuộc vào dự án và các yêu cầu của nó. Đối số cho điều này là câu nói rằng một lập trình viên cơ sở có thể loại bỏ giải pháp trong 50 dòng mã cần một kiến ​​trúc sư 50 lớp.


0

Có một điều như là có quá nhiều chức năng / phương thức riêng tư?

Đúng.

Trong Python, khái niệm "private" (như được sử dụng bởi C ++, Java và C #) không thực sự tồn tại.

Có một quy ước đặt tên "riêng tư", nhưng đó là nó.

Riêng tư có thể dẫn đến mã khó kiểm tra. Nó cũng có thể phá vỡ "Nguyên tắc đóng mở" bằng cách đóng mã đơn giản.

Do đó, đối với những người đã sử dụng Python, các hàm riêng không có giá trị. Chỉ cần làm cho mọi thứ công khai và được thực hiện với nó.


1
Làm thế nào nó có thể phá vỡ các nguyên tắc đóng mở? Mã này là mở cho cả phần mở rộng và đóng để sửa đổi bất kể các phương thức công khai đã được tách thành các phương thức riêng tư nhỏ hơn.
byxor
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.