Truyền chức năng vào các chức năng khác như tham số, thực hành xấu?


40

Chúng tôi đang trong quá trình thay đổi cách ứng dụng AS3 của chúng tôi nói chuyện với back end của chúng tôi và chúng tôi đang trong quá trình triển khai một hệ thống REST để thay thế hệ thống cũ của chúng tôi.

Đáng buồn thay, nhà phát triển đã bắt đầu công việc hiện đang nghỉ ốm dài hạn và nó đã được bàn giao cho tôi. Tôi đã làm việc với nó trong tuần qua hoặc lâu hơn và tôi hiểu hệ thống, nhưng có một điều khiến tôi lo lắng. Dường như có rất nhiều chức năng chuyển vào chức năng. Ví dụ, lớp của chúng ta thực hiện cuộc gọi đến các máy chủ của chúng ta sẽ thực hiện một chức năng mà sau đó nó sẽ gọi và chuyển một đối tượng đến khi quá trình hoàn tất và các lỗi đã được xử lý, v.v.

Nó mang lại cho tôi "cảm giác tồi tệ" nơi tôi cảm thấy như đó là một thực tế khủng khiếp và tôi có thể nghĩ ra một số lý do tại sao nhưng tôi muốn một số xác nhận trước khi tôi đề xuất làm lại hệ thống. Tôi đã tự hỏi nếu có ai có bất kỳ kinh nghiệm với vấn đề có thể?


22
Tại sao bạn cảm thấy như vậy? Bạn có bất kỳ kinh nghiệm với lập trình chức năng? (Tôi sẽ cho là không, nhưng bạn nên xem qua)
Phoshi

8
Sau đó coi đây là một bài học! Những gì học viện dạy và những gì thực sự hữu ích thường có sự chồng chéo nhỏ đáng ngạc nhiên. Có cả một thế giới kỹ thuật lập trình ngoài kia không phù hợp với loại giáo điều đó.
Phoshi

32
Truyền các chức năng cho các chức năng khác là một khái niệm cơ bản mà khi một ngôn ngữ không hỗ trợ nó, mọi người sẽ cố gắng tạo ra các "đối tượng" tầm thường với mục đích duy nhất là chứa chức năng đó như một điểm dừng.
Doval

36
Quay lại thời của tôi, chúng tôi đã gọi những thứ này thông qua các chức năng "Gọi lại"
Mike

Câu trả lời:


84

Đó không phải là một vấn đề.

Đó là một kỹ thuật được biết đến. Đây là các hàm bậc cao hơn (các hàm lấy các hàm làm tham số).

Loại chức năng này cũng là một khối xây dựng cơ bản trong lập trình chức năng và được sử dụng rộng rãi trong các ngôn ngữ chức năng như Haskell .

Các chức năng như vậy không phải là xấu hay tốt - nếu bạn chưa bao giờ gặp phải khái niệm và kỹ thuật thì ban đầu chúng có thể khó nắm bắt, nhưng chúng có thể rất mạnh và là một công cụ tốt để có trong hộp công cụ của bạn.


Cảm ơn vì điều đó, tôi không có kinh nghiệm thực sự với lập trình chức năng nên tôi sẽ xem xét nó. Nó chỉ không hoạt động tốt bởi vì từ kiến ​​thức nhỏ bé của tôi, khi bạn khai báo một cái gì đó là một chức năng riêng tư thì chỉ có lớp đó mới có quyền truy cập vào nó và những cái công khai nên được gọi với tham chiếu đến đối tượng. Tôi sẽ xem xét nó một cách đúng đắn và tôi hy vọng tôi sẽ đánh giá cao nó hơn một chút!
Elliot Blackburn

3
Đọc về sự tiếp nối , phong cách tiếp tục , đơn nguyên (lập trình chức năng) cũng sẽ hữu ích.
Basile Starynkevitch

8
@BlueHat bạn tuyên bố một cái gì đó là riêng tư nếu bạn muốn kiểm soát cách sử dụng nó, nếu việc sử dụng đó là một cuộc gọi lại cho các chức năng cụ thể thì không sao
ratchet freak

5
Chính xác. Đánh dấu nó là riêng tư có nghĩa là bạn không muốn người khác đến và truy cập nó bằng tên bất cứ lúc nào họ muốn. Truyền một chức năng riêng tư cho người khác bởi vì bạn đặc biệt muốn họ gọi nó theo cách họ ghi lại cho tham số đó, là hoàn toàn tốt. Không cần phải khác bất kỳ giá trị nào của trường riêng tư như một tham số cho một số chức năng khác, có lẽ không phù hợp với bạn :-)
Steve Jessop

2
Đáng lưu ý rằng nếu bạn thấy mình viết các lớp cụ thể với các hàm là tham số hàm tạo, có lẽ bạn đang làm sai tm.
Gusdor

30

Chúng không chỉ được sử dụng cho lập trình chức năng. Chúng cũng có thể được gọi là gọi lại :

Gọi lại là một đoạn mã thực thi được truyền dưới dạng đối số cho mã khác, dự kiến ​​sẽ gọi lại (thực thi) đối số vào một thời điểm thuận tiện. Lệnh gọi có thể ngay lập tức như trong một cuộc gọi lại đồng bộ hoặc nó có thể xảy ra vào thời gian sau, như trong một cuộc gọi lại không đồng bộ.

Hãy suy nghĩ về mã không đồng bộ trong một giây. Bạn truyền vào một chức năng, ví dụ, gửi dữ liệu cho người dùng. Chỉ khi mã hoàn thành, bạn mới gọi hàm này với kết quả của phản hồi, sau đó hàm này sẽ sử dụng để gửi dữ liệu lại cho người dùng. Đó là một sự thay đổi tư duy.

Tôi đã viết một thư viện lấy dữ liệu Torrent từ seedbox của bạn. Bạn đang sử dụng vòng lặp sự kiện không chặn để thực thi thư viện này và nhận dữ liệu, sau đó trả lại cho người dùng (giả sử trong ngữ cảnh websocket). Hãy tưởng tượng bạn có 5 người được kết nối trong vòng lặp sự kiện này và một trong những yêu cầu để có được quầy hàng dữ liệu torrent của ai đó. Điều đó sẽ chặn toàn bộ vòng lặp. Vì vậy, bạn cần phải suy nghĩ không đồng bộ và sử dụng các cuộc gọi lại - vòng lặp tiếp tục chạy và "trả lại dữ liệu cho người dùng" chỉ chạy khi chức năng đã thực hiện xong, vì vậy không phải chờ đợi. Cháy và quên đi.


1
+1 Để sử dụng thuật ngữ gọi lại. Tôi gặp thuật ngữ này thường xuyên hơn nhiều so với "các hàm bậc cao hơn".
Rodney Schuler

Tôi thích câu trả lời này, bởi vì OP dường như đang mô tả các cuộc gọi lại, cụ thể, và không chỉ các hàm bậc cao hơn nói chung: "Ví dụ, lớp của chúng tôi thực hiện cuộc gọi đến máy chủ của chúng tôi sẽ thực hiện một chức năng mà sau đó nó sẽ gọi và truyền một đối tượng đến khi quá trình hoàn tất và các lỗi đã được xử lý, v.v. "
Ajedi32

11

Đây không phải là một điều xấu. Trong thực tế, nó là một điều rất tốt.

Truyền các hàm vào các hàm rất quan trọng để lập trình mà chúng tôi đã phát minh ra các hàm lambda như một cách viết tắt. Ví dụ, người ta có thể sử dụng lambdas với thuật toán C ++ để viết mã rất nhỏ gọn nhưng biểu cảm cho phép thuật toán chung có khả năng sử dụng các biến cục bộ và trạng thái khác để thực hiện những việc như tìm kiếm và sắp xếp.

Các thư viện hướng đối tượng cũng có thể có các cuộc gọi lại về cơ bản là các giao diện chỉ định một số lượng nhỏ các hàm (lý tưởng là một, nhưng không phải lúc nào cũng vậy). Sau đó, người ta có thể tạo một lớp đơn giản thực hiện giao diện đó và chuyển một đối tượng của lớp đó qua một hàm. Đó là nền tảng của lập trình hướng sự kiện , trong đó mã mức khung (thậm chí trong một luồng khác) cần phải gọi vào một đối tượng để thay đổi trạng thái để đáp ứng với hành động của người dùng. Giao diện ActionListener của Java là một ví dụ tốt về điều này.

Về mặt kỹ thuật, functor C ++ cũng là một loại đối tượng gọi lại tận dụng đường cú pháp operator()(), để làm điều tương tự.

Cuối cùng, có các con trỏ hàm kiểu C chỉ nên được sử dụng trong C. Tôi sẽ không đi vào chi tiết, tôi chỉ đề cập đến chúng cho đầy đủ. Các tóm tắt khác được đề cập ở trên là vượt trội hơn nhiều và nên được sử dụng trong các ngôn ngữ có chúng.

Những người khác đã đề cập đến lập trình chức năng và cách truyền chức năng là rất tự nhiên trong các ngôn ngữ đó. Lambdas và gọi lại là cách các ngôn ngữ thủ tục và OOP bắt chước điều đó, và chúng rất mạnh mẽ và hữu ích.


Ngoài ra, trong C # đại biểu và lambdas đều được sử dụng rộng rãi.
Evil Dog Pie

6

Như đã nói, nó không phải là thực hành xấu. Nó chỉ đơn thuần là một cách tách rời và tách rời trách nhiệm. Ví dụ: trong OOP bạn sẽ làm một cái gì đó như thế này:

public void doSomethingGeneric(ISpecifier specifier) {
    //do generic stuff
    specifier.doSomethingSpecific();
    //do some other generic stuff
}

Phương thức chung là ủy thác một nhiệm vụ cụ thể - mà nó không biết gì về - cho một đối tượng khác thực hiện giao diện. Phương pháp chung chỉ biết giao diện này. Trong trường hợp của bạn, giao diện này sẽ là một chức năng được gọi.


1
Thành ngữ này chỉ đơn giản là gói chức năng trong một giao diện, hoàn thành một cái gì đó rất giống với việc chuyển một hàm thô trong.

Đúng vậy, tôi chỉ muốn chứng minh rằng đây không phải là một thực hành không phổ biến.
Philipp Murry

3

Nói chung, không có gì sai khi chuyển các chức năng cho các chức năng khác. Nếu bạn đang thực hiện các cuộc gọi không đồng bộ và muốn thực hiện một số kết quả, thì bạn cần một số cơ chế gọi lại.

Có một số nhược điểm tiềm năng của các cuộc gọi lại đơn giản mặc dù:

  • Thực hiện một loạt các cuộc gọi có thể yêu cầu lồng sâu các cuộc gọi lại.
  • Xử lý lỗi có thể cần lặp lại cho mỗi cuộc gọi trong một chuỗi các cuộc gọi.
  • Phối hợp nhiều cuộc gọi là khó xử, như thực hiện nhiều cuộc gọi cùng một lúc và sau đó thực hiện một khi tất cả chúng kết thúc.
  • Không có cách nào chung để hủy một tập hợp các cuộc gọi.

Với các dịch vụ web đơn giản, cách bạn thực hiện nó hoạt động tốt, nhưng sẽ trở nên khó xử nếu bạn cần các chuỗi cuộc gọi phức tạp hơn. Có một số lựa chọn thay thế mặc dù. Ví dụ, với JavaScript, đã có một sự thay đổi trong việc sử dụng các lời hứa ( Điều tuyệt vời về lời hứa javascript ).

Chúng vẫn liên quan đến việc truyền các chức năng cho các chức năng khác nhưng các cuộc gọi không đồng bộ trả về một giá trị nhận cuộc gọi lại thay vì tự gọi lại trực tiếp. Điều này cho phép linh hoạt hơn để kết hợp các cuộc gọi này với nhau. Một cái gì đó như thế này có thể được thực hiện khá dễ dàng trong ActionScript.


Tôi cũng sẽ thêm rằng việc sử dụng các cuộc gọi lại không cần thiết có thể khiến việc theo dõi dòng thực thi đối với bất kỳ ai đọc mã trở nên khó khăn hơn. Một cuộc gọi rõ ràng dễ hiểu hơn một cuộc gọi lại được đặt ở một phần xa của mã. (Điểm dừng để giải cứu!) Tuy nhiên, đây là cách các sự kiện diễn ra trong trình duyệt và các hệ thống dựa trên sự kiện khác; sau đó chúng ta cần hiểu chiến lược của trình phát sự kiện để biết khi nào và theo thứ tự nào xử lý sự kiện sẽ được gọi.
joeytwiddle
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.