Các chức năng dài có được chấp nhận nếu chúng có cấu trúc bên trong?


23

Khi xử lý các thuật toán phức tạp trong các ngôn ngữ có hỗ trợ các hàm lồng nhau (như Python và D), tôi thường viết các hàm lớn (vì thuật toán phức tạp) nhưng giảm thiểu điều này bằng cách sử dụng các hàm lồng nhau để cấu trúc mã phức tạp. Các hàm lớn (hơn 100 dòng) vẫn bị coi là xấu ngay cả khi chúng được cấu trúc tốt trong nội bộ thông qua việc sử dụng các hàm lồng nhau?

Chỉnh sửa: Đối với những người bạn không quen thuộc với Python hoặc D, các hàm lồng nhau trong các ngôn ngữ này cũng cho phép truy cập vào phạm vi chức năng bên ngoài. Trong D truy cập này cho phép đột biến các biến trong phạm vi bên ngoài. Trong Python nó chỉ cho phép đọc. Trong D, bạn có thể vô hiệu hóa quyền truy cập vào phạm vi bên ngoài trong một hàm lồng nhau bằng cách khai báo nó static.


5
Tôi thường xuyên viết hơn 400 hàm / phương thức dòng. Phải đặt câu lệnh chuyển đổi 500 trường hợp đó ở đâu đó :)
Callum Rogers

7
@Callum Rogers: Trong Python, thay vì có 500 câu lệnh chuyển đổi trường hợp, bạn sẽ sử dụng từ điển tra cứu / ánh xạ. Tôi tin rằng họ vượt trội hơn so với tuyên bố trường hợp chuyển đổi 500 hoặc hơn. Bạn vẫn cần 500 dòng định nghĩa từ điển (cách khác, trong Python, bạn có thể sử dụng sự phản chiếu để liệt kê động chúng), nhưng định nghĩa từ điển là dữ liệu, không phải mã; và có rất ít thông tin về việc có định nghĩa dữ liệu lớn hơn định nghĩa hàm lớn. Ngoài ra, dữ liệu mạnh hơn mã.
Nói dối Ryan

Tôi co rúm mỗi khi tôi thấy các hàm có nhiều LỘC, khiến tôi tự hỏi mục đích của mô đun là gì. Nó dễ dàng hơn để tuân theo logic và gỡ lỗi với các hàm nhỏ hơn.
Aditya P

Ngôn ngữ Pascal cổ và quen thuộc cũng cho phép truy cập vào phạm vi bên ngoài và phần mở rộng hàm lồng nhau trong GNU C. (Các ngôn ngữ này chỉ cho phép các phễu chỉ đi xuống, tuy nhiên: đó là các đối số là các hàm mang phạm vi, chỉ có thể là được truyền lại, và không được trả lại, sẽ yêu cầu hỗ trợ đóng từ vựng đầy đủ).
Kaz

Một ví dụ điển hình về các hàm có xu hướng dài là các tổ hợp bạn viết khi thực hiện lập trình phản ứng. Chúng dễ dàng đạt ba mươi dòng, nhưng sẽ tăng gấp đôi kích thước nếu tách ra vì bạn mất đóng.
Craig Gidney

Câu trả lời:


21

Luôn luôn nhớ quy tắc, một chức năng làm một việc và làm tốt điều đó! Nếu bạn có thể làm như vậy, tránh các chức năng lồng nhau.

Nó cản trở khả năng đọc và kiểm tra.


9
Tôi luôn ghét quy tắc này bởi vì một chức năng "một điều" khi được xem ở mức độ trừu tượng cao có thể làm "nhiều việc" khi được xem ở mức thấp hơn. Nếu được áp dụng không chính xác, quy tắc "một điều" có thể dẫn đến các API chi tiết quá mức.
dsimcha

1
@dsimcha: Quy tắc nào không thể áp dụng sai và dẫn đến các vấn đề? Khi ai đó đang áp dụng "quy tắc" / quy tắc vượt qua điểm hữu ích, chỉ cần đưa ra một quy tắc khác: tất cả các khái quát đều sai.

2
@Roger: Một khiếu nại hợp pháp, ngoại trừ quy tắc IMHO thường bị áp dụng sai trong thực tế để biện minh cho các API quá mức, quá mức. Điều này có chi phí cụ thể ở chỗ API trở nên khó sử dụng hơn và dài dòng hơn cho các trường hợp sử dụng đơn giản, phổ biến.
dsimcha

2
"Vâng, quy tắc bị áp dụng sai, nhưng quy tắc bị áp dụng sai quá nhiều!"? : P

1
Chức năng của tôi làm một việc và nó làm điều đó rất tốt: cụ thể là chiếm 27 màn hình. :)
Kaz

12

Một số người đã lập luận rằng các hàm ngắn có thể dễ bị lỗi hơn các hàm dài .

Card and Glass (1990) chỉ ra rằng sự phức tạp trong thiết kế thực sự liên quan đến hai khía cạnh: sự phức tạp trong từng thành phần và sự phức tạp của các mối quan hệ giữa các thành phần.

Cá nhân, tôi đã thấy rằng mã đường thẳng được nhận xét tốt sẽ dễ theo dõi hơn (đặc biệt là khi bạn không phải là người viết nó ban đầu) so với khi nó được chia thành nhiều chức năng không bao giờ được sử dụng ở nơi khác. Nhưng nó thực sự phụ thuộc vào tình hình.

Tôi nghĩ rằng việc cất cánh chính là khi bạn tách một khối mã, bạn đang giao dịch một loại phức tạp khác. Có lẽ có một điểm ngọt ngào ở đâu đó ở giữa.


Bạn đã đọc "Mã sạch" chưa? Nó thảo luận về điều này ở độ dài. amazon.com/Clean-Code-Handbook-Software-Ccraft
Skill / dp / HP

9

Lý tưởng nhất, toàn bộ chức năng nên có thể xem được mà không cần phải cuộn. Đôi khi, điều này là không thể. Nhưng nếu bạn có thể chia nó thành từng mảnh thì nó sẽ giúp việc đọc mã dễ dàng hơn rất nhiều.

Tôi biết rằng ngay khi tôi đẩy Trang lên / Xuống hoặc di chuyển đến một phần khác của mã, tôi chỉ có thể nhớ 7 +/- 2 điều từ trang trước. Và thật không may, một số vị trí sẽ được sử dụng khi đọc mã mới.

Tôi luôn thích nghĩ về bộ nhớ ngắn hạn của mình như các thanh ghi của máy tính (CISC, không phải RISC). Nếu bạn có toàn bộ chức năng trên cùng một trang, bạn có thể vào bộ đệm để lấy thông tin bắt buộc từ một phần khác của chương trình. Nếu toàn bộ chức năng không thể vừa trên một trang, điều đó sẽ tương đương với việc luôn đẩy bất kỳ bộ nhớ nào vào đĩa sau mỗi hoạt động.


3
Bạn chỉ cần in nó ra trên một máy in ma trận - xem, 10 mét mã cùng một lúc :)

Hoặc có được một màn hình với độ phân giải cao hơn
ếchstarr78

Và sử dụng nó theo hướng dọc.
Calmarius

4

Tại sao sử dụng các hàm lồng nhau, thay vì các hàm bên ngoài bình thường?

Ngay cả khi các chức năng bên ngoài chỉ được sử dụng trong một chức năng trước đây của bạn, nó vẫn làm cho toàn bộ mớ hỗn độn dễ đọc hơn:

DoThing(int x){
    x += ;1
    int y = FirstThing(x);
    x = SecondThing(x, y);
    while(spoo > fleem){
        x = ThirdThing(x+y);
    }
}

FirstThing(int x){...}
SecondThing(int x, int y){...}
ThirdThing(int z){...}

2
Hai lý do có thể: làm lộn xộn không gian tên và cái mà tôi sẽ gọi là "sự gần gũi từ vựng". Đây có thể là lý do tốt hoặc xấu, tùy thuộc vào ngôn ngữ của bạn.
Frank Shearar

Nhưng các hàm lồng nhau có thể kém hiệu quả hơn vì chúng cần hỗ trợ các funarg hướng xuống và có thể đóng các từ vựng đầy đủ. Nếu ngôn ngữ được tối ưu hóa kém, nó có thể thực hiện thêm công việc để tạo môi trường động để truyền đến các chức năng con.
Kaz

3

Theo nghiên cứu của ông, tôi không có cuốn sách trước mặt tôi (để trích dẫn), nhưng theo Code Complete, "sweetspot" cho độ dài chức năng là khoảng 25-50 dòng mã theo nghiên cứu của ông.

Có những lúc ok để có chức năng dài:

  • Khi độ phức tạp chu kỳ của hàm thấp. Các nhà phát triển đồng nghiệp của bạn có thể có một chút thất vọng nếu họ phải xem xét một chức năng có chứa một câu lệnh if khổng lồ và câu lệnh khác cho điều đó nếu không xuất hiện trên màn hình cùng một lúc.

Thời gian không ổn khi có các chức năng dài:

  • Bạn có một chức năng với các điều kiện lồng nhau sâu sắc. Làm cho người đọc mã đồng nghiệp của bạn một lợi ích, cải thiện khả năng đọc bằng cách phá vỡ chức năng. Một hàm cung cấp một dấu hiệu cho người đọc rằng "Đây là một khối mã làm một việc". Ngoài ra, hãy tự hỏi nếu độ dài của hàm chỉ ra rằng nó hoạt động quá nhiều và nó cần được đưa vào một lớp khác.

Điểm mấu chốt là khả năng bảo trì phải là một trong những ưu tiên cao nhất trong danh sách của bạn. Nếu một nhà phát triển khác không thể nhìn vào mã của bạn và nhận được "ý chính" về những gì mã đang làm trong chưa đầy 5 giây, thì mã ur không cung cấp đủ "siêu dữ liệu" để cho biết họ đang làm gì. Các nhà phát triển khác sẽ có thể cho biết lớp của bạn đang làm gì chỉ bằng cách nhìn vào trình duyệt đối tượng trong IDE đã chọn của bạn thay vì đọc hơn 100 dòng mã.

Các chức năng nhỏ hơn có những ưu điểm sau:

  • Tính di động: Việc di chuyển chức năng xung quanh dễ dàng hơn nhiều (trong phạm vi lớp tái cấu trúc sang một chức năng khác)
  • Gỡ lỗi: Khi bạn nhìn vào stacktrace, việc xác định lỗi sẽ nhanh hơn nhiều nếu bạn đang xem một hàm có 25 dòng mã thay vì 100.
  • Khả năng đọc - Tên của hàm cho biết toàn bộ khối mã đang làm gì. Một nhà phát triển trong nhóm của bạn có thể không muốn đọc khối đó nếu họ không làm việc với nó. Thêm vào đó, trong hầu hết các IDE hiện đại khác, một nhà phát triển khác có thể hiểu rõ hơn về lớp của bạn đang làm gì bằng cách đọc tên hàm trong trình duyệt đối tượng.
  • Điều hướng - Hầu hết các IDE sẽ cho phép bạn tìm kiếm tên của các hàm. Ngoài ra, hầu hết các IDE hiện đại đều có khả năng xem nguồn của một chức năng trong một cửa sổ khác, điều này giúp các nhà phát triển khác nhìn vào chức năng dài của bạn trên 2 màn hình (nếu đa màn hình) thay vì làm cho chúng cuộn.

Danh sách cứ kéo dài.....


2

Câu trả lời là tùy thuộc, tuy nhiên có lẽ bạn nên biến nó thành một lớp.


Trừ khi bạn không viết bằng OOPL, trong trường hợp đó có thể không :)
Frank Shearar

dsimcha đã đề cập đến việc họ đang sử dụng D và Python.
ếchstarr78

Các lớp học quá thường xuyên dùng nạng cho những người chưa bao giờ nghe về những thứ như đóng cửa từ vựng.
Kaz

2

Tôi không thích hầu hết các hàm lồng nhau. Lambdas thuộc loại đó nhưng thường không gắn cờ tôi trừ khi chúng có hơn 30-40 ký tự.

Lý do cơ bản là nó trở thành một hàm dày đặc cục bộ với đệ quy ngữ nghĩa bên trong, nghĩa là tôi khó có thể bọc bộ não của mình và việc đẩy một số thứ ra chức năng trợ giúp không làm xáo trộn không gian mã là điều dễ dàng hơn .

Tôi nghĩ rằng một chức năng nên làm điều đó. Làm những việc khác là những gì các chức năng khác làm. Vì vậy, nếu bạn có chức năng 200 dòng Thực hiện điều đó và tất cả đều chảy, đó là A-OK.


Đôi khi bạn phải chuyển quá nhiều ngữ cảnh cho một hàm bên ngoài (mà nó có thể có quyền truy cập thuận tiện như một hàm cục bộ) đến mức có nhiều phức tạp hơn trong cách gọi hàm này so với những gì nó làm.
Kaz

1

Có chấp nhận được không? Đó thực sự là một câu hỏi chỉ bạn có thể trả lời. Liệu các chức năng đạt được những gì nó cần phải? Có thể duy trì? Có 'chấp nhận được' đối với các thành viên khác trong nhóm của bạn không? Nếu vậy, đó là những gì thực sự quan trọng.

Chỉnh sửa: Tôi không thấy điều gì về các hàm lồng nhau. Cá nhân, tôi sẽ không sử dụng chúng. Tôi sẽ sử dụng các chức năng thông thường thay thế.


1

Hàm "đường thẳng" dài có thể là một cách rõ ràng để chỉ định một chuỗi các bước dài luôn xảy ra trong một chuỗi cụ thể.

Tuy nhiên, như những người khác đã đề cập, hình thức này có xu hướng phức tạp cục bộ trong đó dòng chảy tổng thể là ít rõ ràng. Đặt độ phức tạp cục bộ đó vào một hàm lồng nhau (nghĩa là: được xác định ở nơi khác trong hàm dài, có thể ở trên cùng hoặc dưới cùng) có thể khôi phục độ rõ cho luồng chính.

Một xem xét quan trọng thứ hai là kiểm soát phạm vi của các biến được dự định chỉ được sử dụng trong một đoạn cục bộ của một hàm dài. Cần thận trọng để tránh có một biến được đưa vào trong một phần của mã vô tình được tham chiếu ở một nơi khác (ví dụ, sau các chu kỳ chỉnh sửa), vì loại lỗi này sẽ không hiển thị dưới dạng lỗi biên dịch hoặc thời gian chạy.

Trong một số ngôn ngữ, vấn đề này dễ dàng được giải quyết: một đoạn mã cục bộ có thể được bọc trong khối riêng của nó, chẳng hạn như với "{...}", trong đó mọi biến được giới thiệu mới chỉ hiển thị cho khối đó. Một số ngôn ngữ, chẳng hạn như Python, thiếu tính năng này, trong trường hợp đó, các hàm cục bộ có thể hữu ích để thực thi các vùng phạm vi nhỏ hơn.


1

Không, các chức năng nhiều trang không được mong muốn và không nên vượt qua đánh giá mã. Tôi cũng đã từng viết các hàm dài, nhưng sau khi đọc Tái cấu trúc của Martin Fowler , tôi dừng lại. Các hàm dài khó viết chính xác, khó hiểu và khó kiểm tra. Tôi chưa bao giờ thấy một hàm gồm 50 dòng sẽ không dễ hiểu và được kiểm tra hơn nếu nó được chia thành một tập hợp các hàm nhỏ hơn. Trong một hàm nhiều trang, gần như chắc chắn toàn bộ các lớp sẽ được tính đến. Thật khó để cụ thể hơn. Có lẽ bạn nên đăng một trong những chức năng dài của mình lên Code Review và ai đó (có thể là tôi) có thể chỉ cho bạn cách cải thiện nó.


0

Khi tôi lập trình bằng python, tôi muốn lùi lại sau khi tôi đã viết một hàm và tự hỏi liệu nó có tuân thủ "Zen of Python" không (nhập 'nhập cái này' trong trình thông dịch python của bạn):

Đẹp thì tốt hơn xấu.
Rõ ràng là tốt hơn so với ngầm.
Đơn giản là tốt hơn phức tạp.
Phức tạp tốt hơn phức tạp.
Bằng phẳng là tốt hơn so với lồng nhau.
Thưa thì tốt hơn dày đặc.
Tính dễ đọc.
Trường hợp đặc biệt không đủ đặc biệt để phá vỡ các quy tắc.
Mặc dù thực tế đánh bại sự tinh khiết.
Lỗi không bao giờ nên âm thầm vượt qua.
Trừ khi im lặng rõ ràng.
Trước sự mơ hồ, hãy từ chối sự cám dỗ để đoán.
Nên có một - và tốt nhất là chỉ có một cách rõ ràng để làm điều đó.
Mặc dù cách đó ban đầu có thể không rõ ràng trừ khi bạn là người Hà Lan.
Bây giờ tốt hơn bao giờ hết.
Mặc dù không bao giờ thường tốt hơn đúnghiện nay.
Nếu việc thực hiện khó giải thích, đó là một ý tưởng tồi.
Nếu việc thực hiện dễ giải thích, nó có thể là một ý tưởng tốt.
Không gian tên là một ý tưởng tuyệt vời - hãy làm nhiều hơn nữa!


1
Nếu rõ ràng là tốt hơn ngầm định, chúng ta nên viết mã bằng ngôn ngữ máy. Ngay cả trong trình biên dịch chương trình; nó thực hiện những điều ngầm ẩn khó chịu như tự động điền vào các vị trí trì hoãn chi nhánh bằng các hướng dẫn, tính toán bù đắp nhánh tương đối và giải quyết các tham chiếu tượng trưng giữa các toàn cầu. Man, những người dùng Python ngu ngốc này và tôn giáo nhỏ của họ ...
Kaz

0

Đặt chúng trong một mô-đun riêng biệt.

Giả sử giải pháp của bạn không phình to hơn nhu cầu bạn không có quá nhiều lựa chọn. Bạn đã phân tách các chức năng trong các chức năng phụ khác nhau, vì vậy câu hỏi là bạn nên đặt nó ở đâu:

  1. Đặt chúng trong một mô-đun chỉ có một chức năng "công khai".
  2. Đặt chúng trong một lớp chỉ có một hàm "công khai" (tĩnh).
  3. Lồng chúng vào một hàm (như bạn đã mô tả).

Bây giờ cả hai lựa chọn thay thế thứ hai và thứ ba trong một số ý nghĩa lồng nhau, nhưng sự thay thế thứ hai sẽ được một số lập trình viên dường như không phải là xấu. Nếu bạn không loại trừ phương án thứ hai, tôi sẽ không thấy quá nhiều lý do để loại trừ phương án thứ ba.

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.