การส่งฟังก์ชั่นไปยังฟังก์ชั่นอื่น ๆ เป็นพารามิเตอร์แนวปฏิบัติที่ไม่ดี


40

เราอยู่ในขั้นตอนของการเปลี่ยนแปลงวิธีที่แอปพลิเคชัน AS3 ของเราพูดถึงกับส่วนหลังของเราและเรากำลังอยู่ในขั้นตอนของการนำระบบ REST ไปใช้เพื่อแทนที่เครื่องเก่าของเรา

น่าเศร้าที่นักพัฒนาที่เริ่มทำงานตอนนี้ลาป่วยระยะยาวและมันก็มอบให้ฉัน ฉันทำงานกับมันมาหลายสัปดาห์แล้วและฉันก็เข้าใจระบบ แต่มีสิ่งหนึ่งที่ทำให้ฉันเป็นกังวล ดูเหมือนจะมีการผ่านฟังก์ชั่นเข้ามาในฟังก์ชั่นได้มากมาย ตัวอย่างเช่นคลาสของเราที่ทำให้การเรียกไปยังเซิร์ฟเวอร์ของเรานั้นมีฟังก์ชั่นที่มันจะทำการเรียกและส่งผ่านออบเจ็กต์เมื่อกระบวนการเสร็จสมบูรณ์และจัดการข้อผิดพลาดเป็นต้น

มันให้ฉันว่า "ความรู้สึกไม่ดี" ที่ฉันรู้สึกว่ามันเป็นวิธีปฏิบัติที่น่ากลัวและฉันสามารถคิดด้วยเหตุผลบางอย่างว่าทำไม แต่ฉันต้องการการยืนยันบางอย่างก่อนที่ฉันจะเสนองานระบบอีกครั้ง ฉันสงสัยว่าใครมีประสบการณ์กับปัญหานี้หรือไม่


22
ทำไมคุณรู้สึกเช่นนั้น คุณมีประสบการณ์เกี่ยวกับการเขียนโปรแกรมฟังก์ชั่นหรือไม่? (ฉันจะไม่คิด แต่คุณควรดู)
Phoshi

8
จากนั้นพิจารณาบทเรียนนี้! สิ่งที่นักวิชาการสอนและสิ่งที่มีประโยชน์จริง ๆ มักจะมีการเหลื่อมกันเล็กน้อยอย่างน่าประหลาดใจ มีโลกทั้งโลกของเทคนิคการเขียนโปรแกรมออกมีที่ไม่สอดคล้องกับความเชื่อนั้น
Phoshi

32
การส่งฟังก์ชั่นไปยังฟังก์ชั่นอื่น ๆ นั้นเป็นแนวคิดพื้นฐานที่เมื่อภาษาไม่รองรับภาษานั้นผู้คนจะออกไปเพื่อสร้าง "วัตถุ" เล็กน้อยที่มีจุดประสงค์เพียงอย่างเดียวคือการบรรจุฟังก์ชั่นที่เป็นปัญหา
Doval

36
ย้อนกลับไปในสมัยของเราเราเรียกสิ่งเหล่านี้ผ่านฟังก์ชั่น "Callbacks"
Mike

คำตอบ:


84

มันไม่ใช่ปัญหา

มันเป็นเทคนิคที่รู้จัก เหล่านี้คือฟังก์ชันลำดับที่สูงขึ้น (ฟังก์ชันที่ใช้ฟังก์ชันเป็นพารามิเตอร์)

ชนิดของฟังก์ชั่นนี้ยังเป็นกลุ่มอาคารพื้นฐานในการเขียนโปรแกรมการทำงานและมีการใช้อย่างกว้างขวางในภาษาทำงานเช่นHaskell

ฟังก์ชั่นดังกล่าวไม่ดีหรือไม่ดี - หากคุณไม่เคยพบกับความคิดและเทคนิคพวกเขาอาจจะเข้าใจได้ยากในตอนแรก แต่มันมีพลังมากและเป็นเครื่องมือที่ดีที่มีอยู่ในชุดเครื่องมือของคุณ


ขอบคุณที่ฉันไม่ได้มีประสบการณ์จริงกับการเขียนโปรแกรมฟังก์ชั่นดังนั้นฉันจะดูมัน มันก็ไม่ได้ดีนักเพราะจากความรู้เล็ก ๆ น้อย ๆ ของฉันเมื่อคุณประกาศบางสิ่งบางอย่างในฐานะฟังก์ชั่นส่วนตัวเฉพาะชั้นเรียนเท่านั้นที่ควรมีสิทธิ์เข้าถึง ฉันจะดูมันอย่างถูกต้องและฉันคาดหวังว่าฉันจะมาชื่นชมมันอีกเล็กน้อย!
Elliot Blackburn

3
อ่านเกี่ยวกับ , ความต่อเนื่องสไตล์ผ่าน , monads (โปรแกรมการทำงาน)นอกจากนี้ยังควรจะมีประโยชน์
Basile Starynkevitch

8
@ BlueHat คุณประกาศสิ่งที่เป็นส่วนตัวถ้าคุณต้องการควบคุมวิธีการใช้งานถ้าการใช้นั้นเป็นการโทรกลับสำหรับฟังก์ชั่นเฉพาะแล้วนั่นก็ดี
ratchet freak

5
เผง การทำเครื่องหมายเป็นส่วนตัวหมายความว่าคุณไม่ต้องการให้คนอื่นเข้ามาและเข้าถึงด้วยชื่อเมื่อใดก็ตามที่พวกเขาต้องการ ส่งผ่านฟังก์ชั่นส่วนตัวไปยังคนอื่นเพราะคุณต้องการให้พวกเขาเรียกมันในแบบที่พวกเขาทำเอกสารสำหรับพารามิเตอร์นั้น ไม่จำเป็นต้องแตกต่างจากการส่งผ่านค่าของเขตข้อมูลส่วนตัวเป็นพารามิเตอร์ไปยังฟังก์ชันอื่นซึ่งสันนิษฐานว่าคุณไม่ได้นั่งกับคุณ :-) ไม่ดี
Steve Jessop

2
น่าสังเกตว่าถ้าคุณพบว่าคุณเขียนคลาสที่เป็นรูปธรรมด้วยฟังก์ชั่นเป็นพารามิเตอร์คอนสตรัคเตอร์คุณอาจทำผิด tm
Gusdor

30

พวกเขาไม่เพียงใช้สำหรับการเขียนโปรแกรมการทำงาน พวกเขายังสามารถเรียกว่าการเรียกกลับ :

การเรียกกลับเป็นชิ้นส่วนของรหัสที่สามารถเรียกใช้งานซึ่งถูกส่งผ่านเป็นอาร์กิวเมนต์ไปยังรหัสอื่นซึ่งคาดว่าจะโทรกลับ (ดำเนินการ) อาร์กิวเมนต์ในเวลาที่สะดวก การเรียกอาจทันทีในการโทรกลับแบบซิงโครนัสหรืออาจเกิดขึ้นในภายหลังเช่นเดียวกับในการโทรกลับแบบอะซิงโครนัส

ลองนึกถึงโค้ดอะซิงโครนัสสักครู่ คุณส่งผ่านฟังก์ชันที่ส่งข้อมูลไปยังผู้ใช้ คุณจะเรียกใช้ฟังก์ชันนี้เมื่อรหัสเสร็จสิ้นแล้วเท่านั้นโดยมีผลการตอบกลับซึ่งฟังก์ชันใช้ในการส่งข้อมูลกลับไปยังผู้ใช้ มันเปลี่ยนความคิด

ฉันเขียนห้องสมุดที่ดึงข้อมูล Torrent จาก seedbox ของคุณ คุณกำลังใช้ลูปของเหตุการณ์ที่ไม่บล็อกเพื่อเรียกใช้ไลบรารีนี้และรับข้อมูลจากนั้นส่งคืนไปยังผู้ใช้ (พูดในบริบท websocket) ลองนึกภาพคุณมี 5 คนที่เชื่อมต่อกันในการวนรอบเหตุการณ์นี้และหนึ่งในคำขอเพื่อรับแผงขายข้อมูลฝนตกหนักของใครบางคน ที่จะปิดกั้นทั้งวง ดังนั้นคุณต้องคิดแบบอะซิงโครนัสและใช้การเรียกกลับ - วนซ้ำยังคงทำงานอยู่และ "ให้ข้อมูลกลับไปยังผู้ใช้" จะทำงานเฉพาะเมื่อฟังก์ชันเสร็จสิ้นการดำเนินการดังนั้นจึงไม่ต้องรอ ไฟและลืม


1
+1 สำหรับการใช้คำศัพท์การโทรกลับ ฉันพบคำนี้บ่อยกว่า "ฟังก์ชั่นการสั่งซื้อที่สูงขึ้น"
Rodney Schuler

ฉันชอบคำตอบนี้เพราะ OP ดูเหมือนจะอธิบายการเรียกกลับโดยเฉพาะและไม่ใช่เพียงแค่ลำดับการทำงานที่สูงขึ้นโดยทั่วไป: "ตัวอย่างเช่นคลาสของเราที่ทำให้การเรียกไปยังเซิร์ฟเวอร์ของเราใช้ในฟังก์ชั่นที่มันจะเรียกและผ่านวัตถุ เมื่อกระบวนการเสร็จสมบูรณ์และมีการจัดการข้อผิดพลาดเป็นต้น "
Ajedi32

11

นี่ไม่ใช่สิ่งเลวร้าย ในความเป็นจริงมันเป็นสิ่งที่ดีมาก

การส่งผ่านฟังก์ชั่นเข้าสู่ฟังก์ชั่นมีความสำคัญต่อการเขียนโปรแกรมซึ่งเราคิดค้นฟังก์ชั่นแลมบ์ดาเป็นชวเลข ตัวอย่างเช่นบางคนอาจใช้ lambdas พร้อมอัลกอริธึม C ++เพื่อเขียนโค้ดที่กะทัดรัด แต่มีความหมายซึ่งอนุญาตให้อัลกอริทึมทั่วไปสามารถใช้ตัวแปรท้องถิ่นและสถานะอื่น ๆ เพื่อทำสิ่งต่าง ๆ เช่นการค้นหาและการเรียงลำดับ

ไลบรารีเชิงวัตถุอาจมีการเรียกกลับซึ่งเป็นอินเตอร์เฟสที่ระบุฟังก์ชันจำนวนน้อย (หนึ่งอัน แต่ไม่เสมอไป) หนึ่งสามารถสร้างคลาสง่าย ๆ ที่ใช้อินเทอร์เฟซนั้นและส่งผ่านวัตถุของคลาสนั้นไปยังฟังก์ชัน นั่นคือรากฐานที่สำคัญของการเขียนโปรแกรมที่ขับเคลื่อนด้วยเหตุการณ์ซึ่งรหัสระดับเฟรมเวิร์ก (อาจอยู่ในเธรดอื่น) จำเป็นต้องเรียกใช้เป็นวัตถุเพื่อเปลี่ยนสถานะเพื่อตอบสนองต่อการกระทำของผู้ใช้ อินเตอร์เฟสActionListenerของ Java เป็นตัวอย่างที่ดีของสิ่งนี้

ในทางเทคนิคแล้ว C ++ functor ก็เป็นประเภทของวัตถุโทรกลับซึ่งใช้ประโยชน์จากน้ำตาล syntactic operator()()เพื่อทำสิ่งเดียวกัน

ในที่สุดก็มีพอยน์เตอร์ฟังก์ชั่น C-style ที่ควรใช้ใน C เท่านั้นฉันจะไม่ลงรายละเอียดเลยฉันแค่พูดถึงมันเพื่อความสมบูรณ์ บทคัดย่ออื่น ๆ ที่กล่าวมาข้างต้นนั้นเหนือกว่าและควรใช้ในภาษาที่มีอยู่

คนอื่น ๆ ได้กล่าวถึงการเขียนโปรแกรมการทำงานและฟังก์ชั่นการส่งผ่านเป็นเรื่องธรรมดามากในภาษาเหล่านั้น แลมบ์ดาและการเรียกกลับเป็นวิธีการดำเนินการและภาษา OOP เลียนแบบนั้นและพวกเขามีประสิทธิภาพและมีประโยชน์มาก


นอกจากนี้ในผู้ได้รับมอบหมาย C # และ lambdas มีการใช้อย่างกว้างขวาง
Evil Dog Pie

6

ดังที่ได้กล่าวไปแล้วว่าไม่ใช่เรื่องเลวร้าย มันเป็นเพียงวิธีแยกแยะและแยกความรับผิดชอบ ตัวอย่างเช่นใน OOP คุณจะทำสิ่งนี้:

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

วิธีการทั่วไปคือการมอบหมายงานเฉพาะ - ซึ่งไม่รู้อะไรเกี่ยวกับ - กับวัตถุอื่นที่ใช้อินเทอร์เฟซ วิธีการทั่วไปรู้เพียงอินเทอร์เฟซนี้ ในกรณีของคุณส่วนต่อประสานนี้จะเป็นฟังก์ชั่นที่จะเรียกใช้


1
สำนวนนี้เป็นเพียงการห่อฟังก์ชั่นในอินเทอร์เฟซ, บรรลุสิ่งที่คล้ายกันมากกับการส่งผ่านฟังก์ชั่นดิบมา

ใช่มันเป็นฉันแค่ต้องการแสดงให้เห็นว่านี่ไม่ใช่การปฏิบัติที่ผิดปกติ
Philipp Murry

3

โดยทั่วไปไม่มีอะไรผิดปกติกับการส่งผ่านฟังก์ชั่นไปยังฟังก์ชั่นอื่น ๆ หากคุณทำการโทรแบบอะซิงโครนัสและต้องการทำบางสิ่งกับผลลัพธ์คุณจะต้องใช้กลไกการติดต่อกลับ

มีข้อเสียเปรียบที่เป็นไปได้บางประการของการโทรกลับอย่างง่าย ๆ แม้ว่า:

  • การโทรออกเป็นชุดอาจต้องใช้การโทรกลับแบบลึก
  • การจัดการข้อผิดพลาดอาจต้องมีการทำซ้ำสำหรับการโทรแต่ละครั้งในลำดับของการโทร
  • การประสานงานการโทรหลายสายนั้นอึดอัดใจเช่นเดียวกับการโทรหลายสายพร้อมกันแล้วทำบางสิ่งเมื่อการโทรทั้งหมดเสร็จสิ้น
  • ไม่มีวิธีทั่วไปในการยกเลิกชุดการโทร

ด้วยเว็บเซอร์ที่เรียบง่ายในแบบที่คุณใช้งานได้ดี แต่มันก็น่าอึดอัดใจถ้าคุณต้องการลำดับการโทรที่ซับซ้อนมากขึ้น มีทางเลือกบางอย่างแม้ว่า ด้วย JavaScript ตัวอย่างเช่นมีการเปลี่ยนไปใช้สัญญา ( มีอะไรดีมากเกี่ยวกับสัญญาจาวาสคริปต์ )

พวกเขายังคงเกี่ยวข้องกับการส่งผ่านฟังก์ชั่นไปยังฟังก์ชั่นอื่น ๆ แต่การโทรแบบอะซิงโครนัสส่งคืนค่าที่รับการเรียกกลับแทนที่จะใช้การเรียกกลับโดยตรง ทำให้มีความยืดหยุ่นมากขึ้นในการเขียนการโทรเหล่านี้เข้าด้วยกัน สิ่งนี้สามารถนำไปใช้งานได้อย่างง่ายดายใน ActionScript


ฉันจะเพิ่มว่าการใช้การเรียกกลับที่ไม่จำเป็นทำให้ยากต่อการติดตามการดำเนินการสำหรับทุกคนที่อ่านโค้ด การโทรที่ชัดเจนเข้าใจง่ายกว่าการโทรกลับที่ตั้งไว้ในส่วนที่ห่างไกลของรหัส (เบรกพอยต์เพื่อช่วยชีวิต!) อย่างไรก็ตามนี่คือเหตุการณ์ที่เกิดขึ้นในเบราว์เซอร์และระบบอิงเหตุการณ์อื่น ๆ จากนั้นเราต้องเข้าใจกลยุทธ์ของตัวปล่อยเหตุการณ์เพื่อที่จะทราบว่าเมื่อใดและในสิ่งที่ตัวจัดการเหตุการณ์ของคำสั่งจะถูกเรียก
joeytwiddle
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.